/*
* Copyright 2015-present Facebook, Inc.
*
* 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.facebook.buck.intellij.ideabuck.format;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckArrayElements;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckProperty;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckPropertyLvalue;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckValue;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckValueArray;
import com.facebook.buck.intellij.ideabuck.lang.psi.BuckVisitor;
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
/** A utility class for sorting buck dependencies alphabetically. */
public class DependenciesOptimizer {
private static final String DEPENDENCIES_KEYWORD = "deps";
private static final String PROVIDED_DEPENDENCIES_KEYWORD = "provided_deps";
private static final String EXPORTED_DEPENDENCIES_KEYWORD = "exported_deps";
private DependenciesOptimizer() {}
public static void optimzeDeps(@NotNull PsiFile file) {
final PropertyVisitor visitor = new PropertyVisitor();
file.accept(
new BuckVisitor() {
@Override
public void visitElement(PsiElement node) {
node.acceptChildren(this);
node.accept(visitor);
}
});
// Commit modifications.
final PsiDocumentManager manager = PsiDocumentManager.getInstance(file.getProject());
manager.doPostponedOperationsAndUnblockDocument(manager.getDocument(file));
}
private static class PropertyVisitor extends BuckVisitor {
@Override
public void visitProperty(@NotNull BuckProperty property) {
BuckPropertyLvalue lValue = property.getPropertyLvalue();
if (lValue == null
|| (!DEPENDENCIES_KEYWORD.equals(lValue.getText())
&& !PROVIDED_DEPENDENCIES_KEYWORD.equals(lValue.getText())
&& !EXPORTED_DEPENDENCIES_KEYWORD.equals(lValue.getText()))) {
return;
}
List<BuckValue> values = property.getExpression().getValueList();
for (BuckValue value : values) {
BuckValueArray array = value.getValueArray();
if (array != null) {
sortArray(array);
}
}
}
}
private static void sortArray(BuckValueArray array) {
BuckArrayElements arrayElements = array.getArrayElements();
PsiElement[] arrayValues = arrayElements.getChildren();
Arrays.sort(arrayValues, PSI_SORT_ORDER);
PsiElement[] oldValues = new PsiElement[arrayValues.length];
for (int i = 0; i < arrayValues.length; ++i) {
oldValues[i] = arrayValues[i].copy();
}
for (int i = 0; i < arrayValues.length; ++i) {
arrayElements.getChildren()[i].replace(oldValues[i]);
}
}
/**
* Use our own method to compare 'deps' strings. 'deps' should be sorted with local references ':'
* preceding any cross-repo references 'cell//' e.g :inner, //world:empty, //world/asia:jp,
* mars//olympus, moon//sea:tranquility
*/
private static int compareDependencyStrings(String baseString, String anotherString) {
int endBaseString = baseString.length();
int endAnotherString = anotherString.length();
int i = 0;
int j = 0;
while (i < endBaseString && j < endAnotherString) {
char c1 = baseString.charAt(i);
char c2 = anotherString.charAt(j);
if (c1 == ' ') {
i++;
continue;
} else if (c2 == ' ') {
j++;
continue;
} else if (c1 == c2) {
i++;
j++;
continue;
} else if (c1 == ':') {
return -1;
} else if (c2 == ':') {
return 1;
} else if (c1 == '/') {
return -1;
} else if (c2 == '/') {
return 1;
} else if (c1 < c2) {
return -1;
} else {
return 1;
}
}
return baseString.compareTo(anotherString);
}
private static final Ordering<String> SORT_ORDER =
Ordering.from(
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return compareDependencyStrings(o1, o2);
}
});
private static final Ordering<PsiElement> PSI_SORT_ORDER =
SORT_ORDER.onResultOf(
new Function<PsiElement, String>() {
@Override
public String apply(@Nullable PsiElement psiElement) {
return psiElement.getText();
}
});
/** Returns the preferred sort order of dependencies. */
public static Ordering<String> sortOrder() {
return SORT_ORDER;
}
}