package com.intellij.lang.javascript.arrangement;
import com.intellij.javascript.flex.mxml.FlexCommonTypeNames;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.generation.ActionScriptGenerateEventHandler;
import com.intellij.lang.javascript.psi.JSFunction;
import com.intellij.lang.javascript.psi.JSParameter;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.JSVariable;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeListOwner;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.arrangement.ArrangementSettingsSerializer;
import com.intellij.psi.codeStyle.arrangement.ArrangementUtil;
import com.intellij.psi.codeStyle.arrangement.DefaultArrangementSettingsSerializer;
import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingRule;
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition;
import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken;
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementSettings;
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.EntryType.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Grouping.GROUP_PROPERTY_FIELD_WITH_GETTER_SETTER;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Modifier.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Order.BY_NAME;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Order.KEEP;
public class ActionScriptRearranger extends JSRearrangerBase {
private static final Logger LOG = Logger.getInstance(ActionScriptRearranger.class.getName());
private static final Set<ArrangementSettingsToken> SUPPORTED_TYPES = ContainerUtilRt.newLinkedHashSet(
CONSTRUCTOR, METHOD, STATIC_INIT, CONST, VAR, PROPERTY, EVENT_HANDLER
);
private static final Set<ArrangementSettingsToken> SUPPORTED_MODIFIERS = ContainerUtilRt.newLinkedHashSet(
PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE, STATIC, FINAL, OVERRIDE
);
private static final Set<ArrangementSettingsToken> VISIBILITY_MODIFIERS = ContainerUtilRt.newLinkedHashSet(
PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE
);
private final StdArrangementSettings DEFAULT_SETTINGS;
private final DefaultArrangementSettingsSerializer SETTINGS_SERIALIZER;
public ActionScriptRearranger() {
final List<ArrangementGroupingRule> groupingRules =
Collections.singletonList(new ArrangementGroupingRule(GROUP_PROPERTY_FIELD_WITH_GETTER_SETTER));
final List<StdArrangementMatchRule> matchRules = getDefaultMatchRules();
DEFAULT_SETTINGS = StdArrangementSettings.createByMatchRules(groupingRules, matchRules);
SETTINGS_SERIALIZER = new DefaultArrangementSettingsSerializer(DEFAULT_SETTINGS);
}
@NotNull
@Override
protected ArrangementSettingsToken detectFieldType(JSVariable variable) {
return variable != null && variable.isConst() ? CONST : VAR;
}
@NotNull
@Override
protected ArrangementSettingsToken detectFunctionType(@NotNull JSFunction function) {
if (isEventHandler(function)) {
return EVENT_HANDLER;
}
return super.detectFunctionType(function);
}
@NotNull
@Override
protected Set<ArrangementSettingsToken> getSupportedTypes() {
return SUPPORTED_TYPES;
}
@NotNull
@Override
protected Set<ArrangementSettingsToken> getVisibilityModifiers() {
return VISIBILITY_MODIFIERS;
}
@NotNull
@Override
protected Set<ArrangementSettingsToken> getSupportedModifiers() {
return SUPPORTED_MODIFIERS;
}
@Nullable
@Override
public StdArrangementSettings getDefaultSettings() {
return DEFAULT_SETTINGS;
}
@NotNull
@Override
public ArrangementSettingsSerializer getSerializer() {
return SETTINGS_SERIALIZER;
}
@Override
public int getBlankLines(@NotNull CodeStyleSettings settings,
@Nullable JSArrangementEntry parent,
@Nullable JSArrangementEntry previous,
@NotNull JSArrangementEntry target) {
if (previous == null) {
return -1;
}
final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(JavaScriptSupportLoader.ECMA_SCRIPT_L4);
ArrangementSettingsToken type = target.getType();
if (VAR.equals(type) || CONST.equals(type)) {
return commonSettings.BLANK_LINES_AROUND_FIELD;
}
else if (STATIC_INIT.equals(type) || CONSTRUCTOR.equals(type) || METHOD.equals(type) || PROPERTY.equals(type)
|| EVENT_HANDLER.equals(type)) {
return commonSettings.BLANK_LINES_AROUND_METHOD;
}
else {
return -1;
}
}
@NotNull
protected Set<ArrangementSettingsToken> detectModifiers(@NotNull final JSAttributeListOwner fieldOrMethod) {
final Set<ArrangementSettingsToken> result = ContainerUtil.newHashSet();
final JSAttributeList attributes = fieldOrMethod.getAttributeList();
if (attributes != null) {
JSAttributeList.AccessType accessType = attributes.getExplicitAccessType();
if (accessType == null) {
final String namespace = attributes.getNamespace();
if (namespace == null) {
accessType = JSAttributeList.AccessType.PACKAGE_LOCAL;
}
}
if (accessType != null) {
switch (accessType) {
case PUBLIC:
result.add(PUBLIC);
break;
case PROTECTED:
result.add(PROTECTED);
break;
case PACKAGE_LOCAL:
result.add(PACKAGE_PRIVATE);
break;
case PRIVATE:
result.add(PRIVATE);
break;
}
}
if (attributes.hasModifier(JSAttributeList.ModifierType.STATIC)) result.add(STATIC);
if (attributes.hasModifier(JSAttributeList.ModifierType.FINAL)) result.add(FINAL);
if (attributes.hasModifier(JSAttributeList.ModifierType.OVERRIDE)) result.add(OVERRIDE);
}
return result;
}
private static boolean isEventHandler(final JSFunction function) {
final JSParameter[] parameters = function.getParameterVariables();
if (parameters.length == 1) {
final PsiElement typeElement = parameters[0].getTypeElement();
if (typeElement instanceof JSReferenceExpression) {
final PsiElement resolve = ((JSReferenceExpression)typeElement).resolve();
if (resolve instanceof JSClass &&
(FlexCommonTypeNames.FLASH_EVENT_FQN.equals(((JSClass)resolve).getQualifiedName()) ||
FlexCommonTypeNames.STARLING_EVENT_FQN.equals(((JSClass)resolve).getQualifiedName()) ||
ActionScriptGenerateEventHandler.isEventClass((JSClass)resolve))) {
return true;
}
}
}
return false;
}
public static List<StdArrangementMatchRule> getDefaultMatchRules() {
// more or less close to Coding Conventions at http://sourceforge.net/adobe/flexsdk/wiki/Coding%20Conventions/
final List<StdArrangementMatchRule> matchRules = new ArrayList<>();
final Set<ArrangementSettingsToken> visibility = VISIBILITY_MODIFIERS;
// static initialization blocks
addRule(matchRules, STATIC_INIT);
// constants
addRule(matchRules, CONST);
// mix-ins (static vars of Function type)
// resources (what's this?)
// static vars
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, VAR, modifier, STATIC);
}
// static properties
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, PROPERTY, modifier, STATIC);
}
// static methods
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, METHOD, modifier, STATIC);
}
// constructor
addRule(matchRules, CONSTRUCTOR);
// vars
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, VAR, modifier);
}
// properties
addRule(matchRules, PROPERTY, OVERRIDE);
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, PROPERTY, modifier);
}
// methods
addRule(matchRules, METHOD, OVERRIDE);
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, METHOD, modifier);
}
// event handlers
addRule(matchRules, EVENT_HANDLER, OVERRIDE);
for (ArrangementSettingsToken modifier : visibility) {
addRule(matchRules, EVENT_HANDLER, modifier);
}
return matchRules;
}
protected boolean isModifierEnabled(@NotNull ArrangementSettingsToken modifier,
@NotNull ArrangementSettingsToken type,
@NotNull Set<ArrangementSettingsToken> existingModifiers) {
if (STATIC_INIT.equals(type)) {
return false;
}
else if (CONST.equals(type)) {
// const can also be static/not static, but there's no sense in non-static constants
return PUBLIC.equals(modifier) || PROTECTED.equals(modifier) || PACKAGE_PRIVATE.equals(modifier) || PRIVATE.equals(modifier);
}
else if (VAR.equals(type)) {
return STATIC.equals(modifier) || PUBLIC.equals(modifier) || PROTECTED.equals(modifier) || PACKAGE_PRIVATE.equals(modifier)
|| PRIVATE.equals(modifier);
}
else if (CONSTRUCTOR.equals(type)) {
return false; // constructor can have visibility modifier, but there's no sense in selecting it 'cuz constructor is only one
}
else if (METHOD.equals(type) || PROPERTY.equals(type) || EVENT_HANDLER.equals(type)) {
if (OVERRIDE.equals(modifier) && existingModifiers.contains(STATIC)) {
return false;
}
else if (STATIC.equals(modifier) && (existingModifiers.contains(OVERRIDE) || existingModifiers.contains(FINAL))) {
return false;
}
else if (FINAL.equals(modifier) && existingModifiers.contains(STATIC)) {
return false;
}
else {
return true;
}
}
else {
LOG.error(type);
return true;
}
}
}