/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.engine.services.correction;
import com.google.common.collect.Lists;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassMember;
import com.google.dart.engine.ast.ClassTypeAlias;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.CompilationUnitMember;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.PartDirective;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.UriBasedDirective;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.error.BooleanErrorListener;
import com.google.dart.engine.parser.Parser;
import com.google.dart.engine.scanner.CharSequenceReader;
import com.google.dart.engine.scanner.CharacterReader;
import com.google.dart.engine.scanner.Scanner;
import com.google.dart.engine.scanner.Token;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Sorter for unit members.
*/
public class MembersSorter {
private static class DirectiveInfo implements Comparable<DirectiveInfo> {
private final Directive directive;
private final DirectivePriority priority;
private final String text;
public DirectiveInfo(Directive directive, DirectivePriority priority, String text) {
this.directive = directive;
this.priority = priority;
this.text = text;
}
@Override
public int compareTo(DirectiveInfo other) {
if (priority == other.priority) {
return text.compareTo(other.text);
}
return priority.ordinal() - other.priority.ordinal();
}
@Override
public String toString() {
return "(priority=" + priority + "; text=" + text + ")";
}
}
private static enum DirectivePriority {
DIRECTIVE_IMPORT_SDK,
DIRECTIVE_IMPORT_PKG,
DIRECTIVE_IMPORT_FILE,
DIRECTIVE_EXPORT_SDK,
DIRECTIVE_EXPORT_PKG,
DIRECTIVE_EXPORT_FILE,
DIRECTIVE_PART,
}
private static class MemberInfo {
PriorityItem item;
String name;
int offset;
int length;
int end;
String text;
public MemberInfo(PriorityItem item, String name, int offset, int length, String text) {
this.item = item;
this.offset = offset;
this.length = length;
this.end = offset + length;
this.name = name;
this.text = text;
}
@Override
public String toString() {
return "(priority=" + item + "; name=" + name + "; offset=" + offset + "; length=" + length
+ ")";
}
}
private static enum MemberKind {
UNIT_FUNCTION_MAIN,
UNIT_ACCESSOR,
UNIT_FUNCTION,
UNIT_FUNCTION_TYPE,
UNIT_CLASS,
UNIT_VARIABLE,
CLASS_ACCESSOR,
CLASS_CONSTRUCTOR,
CLASS_FIELD,
CLASS_METHOD,
}
private static class PriorityItem {
private final MemberKind kind;
private final boolean isPrivate;
private final boolean isStatic;
public PriorityItem(boolean isStatic, MemberKind kind, boolean isPrivate) {
this.kind = kind;
this.isPrivate = isPrivate;
this.isStatic = isStatic;
}
public PriorityItem(boolean isStatic, String name, MemberKind kind) {
this.kind = kind;
this.isPrivate = Identifier.isPrivateName(name);
this.isStatic = isStatic;
}
@Override
public boolean equals(Object obj) {
PriorityItem other = (PriorityItem) obj;
if (kind == MemberKind.CLASS_FIELD) {
return other.kind == kind && other.isStatic == isStatic;
}
return other.kind == kind && other.isPrivate == isPrivate && other.isStatic == isStatic;
}
}
private static final PriorityItem[] PRIORITY_ITEMS = {
// unit
new PriorityItem(false, MemberKind.UNIT_FUNCTION_MAIN, false),
new PriorityItem(false, MemberKind.UNIT_VARIABLE, false),
new PriorityItem(false, MemberKind.UNIT_VARIABLE, true),
new PriorityItem(false, MemberKind.UNIT_ACCESSOR, false),
new PriorityItem(false, MemberKind.UNIT_ACCESSOR, true),
new PriorityItem(false, MemberKind.UNIT_FUNCTION, false),
new PriorityItem(false, MemberKind.UNIT_FUNCTION, true),
new PriorityItem(false, MemberKind.UNIT_FUNCTION_TYPE, false),
new PriorityItem(false, MemberKind.UNIT_FUNCTION_TYPE, true),
new PriorityItem(false, MemberKind.UNIT_CLASS, false),
new PriorityItem(false, MemberKind.UNIT_CLASS, true),
// class
new PriorityItem(true, MemberKind.CLASS_FIELD, false),
new PriorityItem(true, MemberKind.CLASS_ACCESSOR, false),
new PriorityItem(true, MemberKind.CLASS_ACCESSOR, true),
new PriorityItem(false, MemberKind.CLASS_FIELD, false),
new PriorityItem(false, MemberKind.CLASS_CONSTRUCTOR, false),
new PriorityItem(false, MemberKind.CLASS_CONSTRUCTOR, true),
new PriorityItem(false, MemberKind.CLASS_ACCESSOR, false),
new PriorityItem(false, MemberKind.CLASS_ACCESSOR, true),
new PriorityItem(false, MemberKind.CLASS_METHOD, false),
new PriorityItem(false, MemberKind.CLASS_METHOD, true),
new PriorityItem(true, MemberKind.CLASS_METHOD, false),
new PriorityItem(true, MemberKind.CLASS_METHOD, true),};
private static int getPriority(PriorityItem item) {
for (int i = 0; i < PRIORITY_ITEMS.length; i++) {
if (PRIORITY_ITEMS[i].equals(item)) {
return i;
}
}
throw new IllegalArgumentException("Unknown priority item: " + item);
}
private static List<MemberInfo> getSortedMembers(List<MemberInfo> members) {
List<MemberInfo> membersSorted = Lists.newArrayList(members);
Collections.sort(membersSorted, new Comparator<MemberInfo>() {
@Override
public int compare(MemberInfo o1, MemberInfo o2) {
int priority1 = getPriority(o1.item);
int priority2 = getPriority(o2.item);
if (priority1 == priority2) {
// don't reorder class fields
if (o1.item.kind == MemberKind.CLASS_FIELD) {
return 0;
}
// sort all other members by name
String name1 = o1.name.toLowerCase();
String name2 = o2.name.toLowerCase();
return name1.compareTo(name2);
}
return priority1 - priority2;
}
});
return membersSorted;
}
private final String initialCode;
private final CompilationUnit unit;
private String code;
/**
* Initialize a newly created {@link MembersSorter}. Creates
*
* @param code the Dart code
* @param unit an optional parsed {@link CompilationUnit} for the given "code", may be
* {@code null}
*/
public MembersSorter(String code, CompilationUnit unit) {
this.initialCode = code;
if (unit != null) {
this.unit = unit;
} else {
this.unit = parseUnit(code);
}
this.code = code;
}
/**
* Returns the sorted source or {@code null} if no changes.
*/
public String createSortedCode() {
if (unit == null) {
return null;
}
sortClassesMembers();
sortUnitMembers();
// Must sort unit directives last because it may insert newlines, which
// would confuse the offsets used by the other sort functions.
sortUnitDirectives();
// is the any change?
if (code.equals(initialCode)) {
return null;
}
return code;
}
/**
* Sorts all members of all {@link ClassDeclaration}s.
*/
public void sortClassesMembers() {
for (CompilationUnitMember unitMember : unit.getDeclarations()) {
if (unitMember instanceof ClassDeclaration) {
ClassDeclaration classDeclaration = (ClassDeclaration) unitMember;
sortClassMembers(classDeclaration);
}
}
}
/**
* @return the EOL to use for {@link #code}.
*/
private String getEndOfLine() {
if (code.contains("\r\n")) {
return "\r\n";
} else {
return "\n";
}
}
private CompilationUnit parseUnit(String code) {
BooleanErrorListener listener = new BooleanErrorListener();
CharacterReader reader = new CharSequenceReader(code);
Scanner scanner = new Scanner(null, reader, listener);
Token token = scanner.tokenize();
if (listener.getErrorReported()) {
return null;
}
Parser parser = new Parser(null, listener);
CompilationUnit unit = parser.parseCompilationUnit(token);
if (listener.getErrorReported()) {
return null;
}
return unit;
}
private void sortAndReorderMembers(List<MemberInfo> members) {
List<MemberInfo> membersSorted = getSortedMembers(members);
int size = membersSorted.size();
for (int i = 0; i < size; i++) {
MemberInfo newInfo = membersSorted.get(size - 1 - i);
MemberInfo oldInfo = members.get(size - 1 - i);
code = code.substring(0, oldInfo.offset) + newInfo.text + code.substring(oldInfo.end);
}
}
/**
* Sorts all members of the given {@link ClassDeclaration}.
*/
private void sortClassMembers(ClassDeclaration classDeclaration) {
List<MemberInfo> members = Lists.newArrayList();
for (ClassMember member : classDeclaration.getMembers()) {
MemberKind kind = null;
boolean isStatic = false;
String name = null;
if (member instanceof ConstructorDeclaration) {
kind = MemberKind.CLASS_CONSTRUCTOR;
SimpleIdentifier nameNode = ((ConstructorDeclaration) member).getName();
if (nameNode == null) {
name = "";
} else {
name = nameNode.getName();
}
}
if (member instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) member;
List<VariableDeclaration> fields = fieldDeclaration.getFields().getVariables();
if (!fields.isEmpty()) {
kind = MemberKind.CLASS_FIELD;
isStatic = fieldDeclaration.isStatic();
name = fields.get(0).getName().getName();
}
}
if (member instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) member;
isStatic = method.isStatic();
name = method.getName().getName();
if (method.isGetter()) {
kind = MemberKind.CLASS_ACCESSOR;
name += " getter";
} else if (method.isSetter()) {
kind = MemberKind.CLASS_ACCESSOR;
name += " setter";
} else {
kind = MemberKind.CLASS_METHOD;
}
}
if (name != null) {
PriorityItem item = new PriorityItem(isStatic, name, kind);
int offset = member.getOffset();
int length = member.getLength();
String text = code.substring(offset, offset + length);
members.add(new MemberInfo(item, name, offset, length, text));
}
}
// do sort
sortAndReorderMembers(members);
}
/**
* Sorts all {@link Directive}s.
*/
private void sortUnitDirectives() {
List<DirectiveInfo> directives = Lists.newArrayList();
for (Directive directive : unit.getDirectives()) {
if (!(directive instanceof UriBasedDirective)) {
continue;
}
UriBasedDirective uriDirective = (UriBasedDirective) directive;
String uriContent = uriDirective.getUri().getStringValue();
DirectivePriority kind = null;
if (directive instanceof ImportDirective) {
if (uriContent.startsWith("dart:")) {
kind = DirectivePriority.DIRECTIVE_IMPORT_SDK;
} else if (uriContent.startsWith("package:")) {
kind = DirectivePriority.DIRECTIVE_IMPORT_PKG;
} else {
kind = DirectivePriority.DIRECTIVE_IMPORT_FILE;
}
}
if (directive instanceof ExportDirective) {
if (uriContent.startsWith("dart:")) {
kind = DirectivePriority.DIRECTIVE_EXPORT_SDK;
} else if (uriContent.startsWith("package:")) {
kind = DirectivePriority.DIRECTIVE_EXPORT_PKG;
} else {
kind = DirectivePriority.DIRECTIVE_EXPORT_FILE;
}
}
if (directive instanceof PartDirective) {
kind = DirectivePriority.DIRECTIVE_PART;
}
if (kind != null) {
int offset = directive.getOffset();
int length = directive.getLength();
String text = code.substring(offset, offset + length);
directives.add(new DirectiveInfo(directive, kind, text));
}
}
// nothing to do
if (directives.isEmpty()) {
return;
}
int firstDirectiveOffset = directives.get(0).directive.getOffset();
int lastDirectiveEnd = directives.get(directives.size() - 1).directive.getEnd();
// do sort
Collections.sort(directives);
// append directives with grouping
String directivesCode;
{
StringBuilder sb = new StringBuilder();
String endOfLine = getEndOfLine();
DirectivePriority currentPriority = null;
for (DirectiveInfo directive : directives) {
if (currentPriority != directive.priority) {
if (sb.length() != 0) {
sb.append(endOfLine);
}
currentPriority = directive.priority;
}
sb.append(directive.text);
sb.append(endOfLine);
}
directivesCode = sb.toString();
directivesCode = StringUtils.chomp(directivesCode);
}
code = code.substring(0, firstDirectiveOffset) + directivesCode
+ code.substring(lastDirectiveEnd);
}
/**
* Sorts all {@link CompilationUnitMember}s.
*/
private void sortUnitMembers() {
List<MemberInfo> members = Lists.newArrayList();
for (CompilationUnitMember member : unit.getDeclarations()) {
MemberKind kind = null;
String name = null;
if (member instanceof ClassDeclaration) {
kind = MemberKind.UNIT_CLASS;
name = ((ClassDeclaration) member).getName().getName();
}
if (member instanceof ClassTypeAlias) {
kind = MemberKind.UNIT_CLASS;
name = ((ClassTypeAlias) member).getName().getName();
}
if (member instanceof FunctionDeclaration) {
FunctionDeclaration function = (FunctionDeclaration) member;
name = function.getName().getName();
if (function.isGetter()) {
kind = MemberKind.UNIT_ACCESSOR;
name += " getter";
} else if (function.isSetter()) {
kind = MemberKind.UNIT_ACCESSOR;
name += " setter";
} else {
if (name.equals("main")) {
kind = MemberKind.UNIT_FUNCTION_MAIN;
} else {
kind = MemberKind.UNIT_FUNCTION;
}
}
}
if (member instanceof FunctionTypeAlias) {
kind = MemberKind.UNIT_FUNCTION_TYPE;
name = ((FunctionTypeAlias) member).getName().getName();
}
if (member instanceof TopLevelVariableDeclaration) {
TopLevelVariableDeclaration variableDeclaration = (TopLevelVariableDeclaration) member;
List<VariableDeclaration> variables = variableDeclaration.getVariables().getVariables();
if (!variables.isEmpty()) {
kind = MemberKind.UNIT_VARIABLE;
name = variables.get(0).getName().getName();
}
}
if (name != null) {
PriorityItem item = new PriorityItem(false, name, kind);
int offset = member.getOffset();
int length = member.getLength();
String text = code.substring(offset, offset + length);
members.add(new MemberInfo(item, name, offset, length, text));
}
}
// do sort
sortAndReorderMembers(members);
}
}