| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.idea.gradle.parser; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiElement; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner; |
| |
| import java.util.List; |
| |
| /** |
| * This is a generic class that knows how to convert items in a Gradle buildfile closure into Java-domain objects of a given type. |
| * It can take a closure and turn it into a list of Java objects, or given a list of Java objects, write them into the closure. |
| */ |
| public abstract class ValueFactory<E> { |
| |
| /** |
| * Specifies a filter that allows greater control over whether certain keys get written to the build file or not. Intended for use in |
| * composite types such as {@link com.android.tools.idea.gradle.parser.NamedObject} to prevent writing out of sub-keys that don't need |
| * to be written (necessary since you normally invoke a call to write out the entire object). If you avoid overwriting unmodified keys, |
| * then you won't stomp on user-specified script that the user didn't intend to change. |
| */ |
| public interface KeyFilter { |
| boolean shouldWriteKey(BuildFileKey key, Object object); |
| } |
| |
| @NotNull |
| public List<E> getValues(@NotNull GrStatementOwner closure) { |
| List<E> result = Lists.newArrayList(); |
| for (PsiElement element : closure.getChildren()) { |
| List<E> values = getValues(element); |
| if (values != null && !values.isEmpty()) { |
| result.addAll(values); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * <p>This implementation of setValues calls {@link #setValue(org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner, Object)}, |
| * which is suitable for cases where the value uniquely identifies a statement in the buildfile, such as a "named object" that consists |
| * of a method call whose name is the name of the object, and a closure consisting of key/value statements; examples of named objects |
| * include build types and flavors. In other cases, such as repositories and dependencies, we can't tell by looking at an individual |
| * value what statement in the build file it corresponds to: there's no name or other information that makes it unique. setValue |
| * won't work for writing those types of objects into build files, and for those types, this class will need to be subclassed and this |
| * method overridden to work a different way.</p> |
| * <p>It also calls {@link #removeValue(org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner, Object)} to remove objects |
| * that aren't in the passed-in list.</p> |
| */ |
| public void setValues(@NotNull GrStatementOwner closure, @NotNull List<E> values, @Nullable KeyFilter filter) { |
| for (E value : values) { |
| setValue(closure, value, filter); |
| } |
| for (E existingValue : findValuesToDelete(closure, values)) { |
| removeValue(closure, existingValue); |
| } |
| GradleGroovyFile.reformatClosure(closure); |
| } |
| |
| /** |
| * This method is called when all of the values underneath the key are being replaced via a call to |
| * {@link #setValues(org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner, java.util.List, com.android.tools.idea.gradle.parser.ValueFactory.KeyFilter)}. |
| * It looks for values that are in the build file that are not in the replacement values; these entries in the build file need to be |
| * removed. The method returns those need-to-be-deleted objects. The base implementation does an {@link Object#equals(Object)} test on |
| * objects to determine if a build file object exists in the replacement value list or not; subclasses can override this to provide more |
| * specialized behavior. |
| */ |
| protected Iterable<E> findValuesToDelete(@NotNull GrStatementOwner closure, @NotNull final List<E> replacementValues) { |
| return Iterables.filter(getValues(closure), new Predicate<E>() { |
| @Override |
| public boolean apply(E input) { |
| return !replacementValues.contains(input); |
| } |
| }); |
| } |
| |
| /** |
| * If your subclass supports parsing values from string, override this method to implement that functionality |
| */ |
| @NotNull |
| public E parse(@NotNull String s, Project project) { |
| throw new UnsupportedOperationException("parse not implemented"); |
| } |
| |
| /** |
| * See {@link #setValues(org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner, java.util.List)} for documentation. |
| */ |
| protected abstract void setValue(@NotNull GrStatementOwner closure, @NotNull E value, @Nullable KeyFilter filter); |
| |
| /** |
| * Given a PSI element that represents a single statement or line of code, returns the Java objects parsed from that element. This |
| * element is generally a {@link org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement} or a |
| * {@link com.intellij.psi.PsiComment}. |
| */ |
| @Nullable |
| abstract protected List<E> getValues(@NotNull PsiElement statement); |
| |
| /** |
| * If your subclass supports removal of individual objects, override this method to implement that functionality. |
| */ |
| protected void removeValue(@NotNull GrStatementOwner closure, @NotNull E value) { |
| throw new UnsupportedOperationException("removeValue not implemented"); |
| } |
| } |