/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* 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.intellij.plugins.haxe.lang.psi;
import com.intellij.plugins.haxe.util.HaxeDebugLogger;
import com.intellij.plugins.haxe.util.HaxeResolveUtil;
import com.intellij.psi.JavaResolveResult;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiSubstitutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author: Fedor.Korotkov
*/
public class HaxeClassResolveResult implements Cloneable {
private static final HaxeDebugLogger LOG = HaxeDebugLogger.getLogger();
public static final HaxeClassResolveResult EMPTY = new HaxeClassResolveResult(null);
@Nullable
private final HaxeClass haxeClass;
private final HaxeGenericSpecialization specialization;
private final List<HaxeClassResolveResult> functionTypes = new ArrayList<HaxeClassResolveResult>();
private HaxeClassResolveResult(@Nullable HaxeClass aClass) {
this(aClass, new HaxeGenericSpecialization());
}
private HaxeClassResolveResult(@Nullable HaxeClass aClass, HaxeGenericSpecialization specialization) {
haxeClass = aClass;
this.specialization = specialization;
}
@Override
protected HaxeClassResolveResult clone() {
return new HaxeClassResolveResult(haxeClass, specialization.clone());
}
@NotNull
public static HaxeClassResolveResult create(@Nullable HaxeClass aClass) {
return create(aClass, new HaxeGenericSpecialization());
}
private static int debugNestCountForCreate = 0;
@NotNull
public static HaxeClassResolveResult create(@Nullable HaxeClass aClass, HaxeGenericSpecialization specialization) {
if (aClass == null) {
return new HaxeClassResolveResult(null);
}
debugNestCountForCreate += 1;
LOG.debug(debugNestCountForCreate + "Resolving class " + aClass.getName() + " using specialization " + (specialization == null ? "<none>" : specialization.debugDump(" ")));
HaxeClassResolveResult resolveResult = HaxeClassResolveCache.getInstance(aClass.getProject()).get(aClass);
if (resolveResult == null) {
resolveResult = new HaxeClassResolveResult(aClass);
HaxeClassResolveCache.getInstance(aClass.getProject()).put(aClass, resolveResult);
final HaxeGenericParam genericParam = aClass.getGenericParam();
List<HaxeGenericListPart> genericListPartList = genericParam != null ?
genericParam.getGenericListPartList() :
Collections.<HaxeGenericListPart>emptyList();
for (HaxeGenericListPart genericListPart : genericListPartList) {
final HaxeComponentName componentName = genericListPart.getComponentName();
final HaxeTypeListPart typeListPart = genericListPart.getTypeListPart();
final List<HaxeTypeOrAnonymous> typeOrAnonymousList = ((typeListPart != null) ? typeListPart.getTypeOrAnonymousList() : null);
final HaxeTypeOrAnonymous typeOrAnonymous = ((typeOrAnonymousList != null) ? typeOrAnonymousList.get(0) : null);
final HaxeType specializedType = ((typeOrAnonymous != null) ? typeOrAnonymous.getType() : null);
if (specializedType != null) {
HaxeClassResolveResult specializedTypeResult = HaxeResolveUtil.getHaxeClassResolveResult(specializedType, specialization);
LOG.debug(debugNestCountForCreate + " Adding specialization for " + aClass.getName() + "<" + componentName.getName() + "> -> " + specializedTypeResult.debugDump(" "));
resolveResult.specialization.put(aClass,
componentName.getName(),
specializedTypeResult);
} else {
LOG.debug(debugNestCountForCreate + " Not adding specialization for " + aClass.getName() + "<" + componentName.getName() + ">. No specialized type found.");
}
}
for (HaxeType haxeType : aClass.getHaxeExtendsList()) {
final HaxeClassResolveResult result = create(HaxeResolveUtil.tryResolveClassByQName(haxeType));
result.specializeByParameters(haxeType.getTypeParam());
LOG.debug(debugNestCountForCreate + " Adding (extends) specialization for " + aClass.getName() + "<" + haxeType.getName() + "> -> " + result.getSpecialization().debugDump(" "));
resolveResult.merge(result.getSpecialization());
}
for (HaxeType haxeType : aClass.getHaxeImplementsList()) {
final HaxeClassResolveResult result = create(HaxeResolveUtil.tryResolveClassByQName(haxeType));
result.specializeByParameters(haxeType.getTypeParam());
LOG.debug(debugNestCountForCreate + " Adding (implements) specialization for " + aClass.getName() + "<" + haxeType.getName() + "> -> " + result.getSpecialization().debugDump(" "));
resolveResult.merge(result.getSpecialization());
}
}
final HaxeClassResolveResult clone = resolveResult.clone();
clone.softMerge(specialization);
debugNestCountForCreate -= 1;
return clone;
}
public List<HaxeClassResolveResult> getFunctionTypes() {
return functionTypes;
}
private void merge(HaxeGenericSpecialization otherSpecializations) {
for (String key : otherSpecializations.map.keySet()) {
specialization.map.put(key, otherSpecializations.map.get(key));
}
}
private void softMerge(HaxeGenericSpecialization otherSpecializations) {
for (String key : otherSpecializations.map.keySet()) {
if (!specialization.map.containsKey(key)) {
specialization.map.put(key, otherSpecializations.map.get(key));
}
}
}
@Nullable
public HaxeClass getHaxeClass() {
return haxeClass;
}
public HaxeGenericSpecialization getSpecialization() {
return specialization;
}
public void specialize(@Nullable PsiElement element) {
if (element == null || haxeClass == null || !haxeClass.isGeneric()) {
return;
}
if (element instanceof HaxeNewExpression) {
specializeByParameters(((HaxeNewExpression)element).getType().getTypeParam());
}
}
public void specializeByParameters(@Nullable HaxeTypeParam param) {
if (param == null || haxeClass == null || !haxeClass.isGeneric()) {
return;
}
final HaxeGenericParam genericParam = haxeClass.getGenericParam();
assert genericParam != null;
final HaxeTypeList typeList = param.getTypeList();
int size = Math.min(genericParam.getGenericListPartList().size(), typeList.getTypeListPartList().size());
for (int i = 0; i < size; i++) {
final HaxeGenericListPart genericListPart = genericParam.getGenericListPartList().get(i);
final HaxeTypeListPart typeListPart = typeList.getTypeListPartList().get(i);
final List<HaxeTypeOrAnonymous> typeOrAnonymousList = ((typeListPart != null) ? typeListPart.getTypeOrAnonymousList() : null);
final HaxeTypeOrAnonymous typeOrAnonymous = (((typeOrAnonymousList != null) && (typeOrAnonymousList.size() > 0)) ? typeOrAnonymousList.get(0) : null);
final HaxeType specializedType = ((typeOrAnonymous != null) ? typeOrAnonymous.getType() : null);
if (genericListPart.getText() == null || specializedType == null) continue;
final HaxeClassResolveResult specializedTypeResult = HaxeResolveUtil.getHaxeClassResolveResult(specializedType, specialization);
specialization.put(haxeClass, genericListPart.getText(), specializedTypeResult);
}
LOG.debug(specialization.debugDump());
}
public boolean isFunctionType() {
return !functionTypes.isEmpty();
}
public String debugDump() {
return debugDump(null);
}
public String debugDump( String linePrefix ) {
StringBuilder builder = new StringBuilder();
if (linePrefix == null) {
linePrefix="";
}
builder.append(linePrefix);
builder.append(null == haxeClass ? "<null haxeClass>" : haxeClass.getName());
builder.append(":\n");
String prefix = linePrefix + " ";
builder.append(null == specialization ? "<null specialization>" : specialization.debugDump(prefix));
for(HaxeClassResolveResult result : functionTypes) {
result.debugDump(prefix + " ");
}
return builder.toString();
}
public JavaResolveResult toJavaResolveResult() {
return new JavaResult(this);
}
private class JavaResult implements JavaResolveResult {
private HaxeClassResolveResult originalResult = null;
public JavaResult(HaxeClassResolveResult result) { originalResult = result; }
@Override public PsiElement getElement() { return (originalResult != null ? originalResult.getHaxeClass() : null); }
@NotNull
@Override public PsiSubstitutor getSubstitutor() { return PsiSubstitutor.EMPTY; }
@Override public boolean isValidResult() { return null != this.getElement(); }
@Override public boolean isAccessible() { return true; }
@Override public boolean isStaticsScopeCorrect() { return true; } // TODO: How to check scope?
@Override public PsiElement getCurrentFileResolveScope() { return (this.getElement() != null ? this.getElement().getOriginalElement() : null); } // TODO: Verify
@Override public boolean isPackagePrefixPackageReference() { return false; } // TODO: No idea what to do with this.
}
}