blob: f8b61e110c1fdbcc35dc179ed29ffab45484abc4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.ruby.internal.parser.mixin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.mixin.IMixinElement;
import org.eclipse.dltk.core.mixin.MixinModel;
import org.eclipse.dltk.ruby.typeinference.RubyClassType;
import org.eclipse.dltk.ti.BasicContext;
import org.eclipse.dltk.ti.DLTKTypeInferenceEngine;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
public class RubyMixinClass implements IRubyMixinElement {
private final String key;
protected final RubyMixinModel model;
private final boolean module;
public RubyMixinClass(RubyMixinModel model, String key, boolean module) {
super();
this.model = model;
this.key = key;
this.module = module;
}
@Override
public String getKey() {
return key;
}
public boolean isModule() {
return module;
}
public RubyMixinClass getInstanceClass() {
if (!isMeta())
return this;
String newkey = key + RubyMixin.INSTANCE_SUFFIX;
IRubyMixinElement r = model.createRubyElement(newkey);
if (r instanceof RubyMixinClass)
return (RubyMixinClass) r;
return null;
}
public RubyMixinClass getMetaclass() {
if (isMeta())
return this;
String metakey = key.substring(0, key
.indexOf(RubyMixin.INSTANCE_SUFFIX));
IRubyMixinElement r = model.createRubyElement(metakey);
if (r instanceof RubyMixinClass)
return (RubyMixinClass) r;
return null;
}
public boolean isMeta() {
return (!key.endsWith(RubyMixin.INSTANCE_SUFFIX))
&& (!key.endsWith(RubyMixin.VIRTUAL_SUFFIX));
}
public String getName() {
String name;
if (key.indexOf(MixinModel.SEPARATOR) != -1)
name = key.substring(key.lastIndexOf(MixinModel.SEPARATOR));
else
name = key;
int pos;
if ((pos = name.indexOf(RubyMixin.INSTANCE_SUFFIX)) != -1)
name = name.substring(0, pos);
return name;
}
public IType[] getSourceTypes() {
List<IType> result = new ArrayList<IType>();
IMixinElement mixinElement = model.getRawModel().get(key);
Object[] allObjects = mixinElement.getAllObjects();
for (int i = 0; i < allObjects.length; i++) {
RubyMixinElementInfo info = (RubyMixinElementInfo) allObjects[i];
if (info == null)
continue;
if (info.getKind() == RubyMixinElementInfo.K_CLASS
|| info.getKind() == RubyMixinElementInfo.K_MODULE) {
if (info.getObject() != null)
result.add((IType) info.getObject());
}
}
return result.toArray(new IType[result.size()]);
}
public RubyMixinClass getSuperclass() {
IMixinElement mixinElement = model.getRawModel().get(key);
if (mixinElement == null)
return null;
Object[] allObjects = mixinElement.getAllObjects();
// IType type = null;
for (int i = 0; i < allObjects.length; i++) {
RubyMixinElementInfo info = (RubyMixinElementInfo) allObjects[i];
if (info == null) {
continue;
}
// if (info.getKind() == RubyMixinElementInfo.K_CLASS) {
// type = (IType) info.getObject();
// if (type == null)
// continue;
// String key = RubyModelUtils.evaluateSuperClass(type);
// if (key == null)
// continue;
// if (!this.isMeta())
// key = key + RubyMixin.INSTANCE_SUFFIX;
// RubyMixinClass s = (RubyMixinClass) model.createRubyElement(key);
// return s;
// }
if (info.getKind() == RubyMixinElementInfo.K_SUPER) {
SuperclassReferenceInfo sinfo = (SuperclassReferenceInfo) info
.getObject();
BasicContext c = new BasicContext(sinfo.getModule(), sinfo
.getDecl());
ExpressionTypeGoal g = new ExpressionTypeGoal(c, sinfo
.getNode());
DLTKTypeInferenceEngine engine = new DLTKTypeInferenceEngine();
IEvaluatedType type2 = engine.evaluateType(g, 500);
if (type2 instanceof RubyClassType) {
RubyClassType rubyClassType = (RubyClassType) type2;
String key = rubyClassType.getModelKey();
if (!this.isMeta())
key = key + RubyMixin.INSTANCE_SUFFIX;
return (RubyMixinClass) model.createRubyElement(key);
}
}
}
String key;
Set<String> includeSet = new HashSet<String>();
RubyMixinClass[] includedClasses = model.createRubyClass(
new RubyClassType("Object%")).getIncluded(); //$NON-NLS-1$
for (int cnt = 0, max = includedClasses.length; cnt < max; cnt++) {
includeSet.add(includedClasses[cnt].getKey());
}
if (this.isMeta())
if (this.isModule())
key = "Module%"; //$NON-NLS-1$
else
key = "Class%"; //$NON-NLS-1$
else if (isModule()
&& ("Kernel%".equals(this.key) || includeSet.contains(this.getKey()))) //$NON-NLS-1$
return null;
else
key = "Object"; //$NON-NLS-1$
if (!this.isMeta())
key = key + RubyMixin.INSTANCE_SUFFIX;
RubyMixinClass s = (RubyMixinClass) model.createRubyElement(key);
return s;
}
/**
* Returns modules included into this class.
*
* @return
*/
public RubyMixinClass[] getIncluded() {
IMixinElement mixinElement = model.getRawModel().get(key);
if (mixinElement == null)
return new RubyMixinClass[0];
List<RubyMixinClass> result = new ArrayList<RubyMixinClass>();
HashSet<String> names = new HashSet<String>();
Object[] allObjects = mixinElement.getAllObjects();
for (int i = 0; i < allObjects.length; i++) {
RubyMixinElementInfo info = (RubyMixinElementInfo) allObjects[i];
if (info == null) {
continue;
}
if (info.getKind() == RubyMixinElementInfo.K_INCLUDE) {
String inclKey = (String) info.getObject();
if (names.add(inclKey)) {
if (/* !this.isMeta() && */!inclKey
.endsWith(RubyMixin.INSTANCE_SUFFIX))
inclKey += RubyMixin.INSTANCE_SUFFIX;
IRubyMixinElement element = model
.createRubyElement(inclKey);
// TODO if element is not found - try to use different path
// combinations
if (element instanceof RubyMixinClass)
result.add((RubyMixinClass) element);
}
}
}
return result.toArray(new RubyMixinClass[result.size()]);
}
/**
* Returns modules this class is extended from.
*
* @return
*/
public RubyMixinClass[] getExtended() {
IMixinElement mixinElement = model.getRawModel().get(key);
if (mixinElement == null)
return new RubyMixinClass[0];
List<RubyMixinClass> result = new ArrayList<RubyMixinClass>();
HashSet<String> names = new HashSet<String>();
Object[] allObjects = mixinElement.getAllObjects();
for (int i = 0; i < allObjects.length; i++) {
RubyMixinElementInfo info = (RubyMixinElementInfo) allObjects[i];
if (info == null) {
continue;
}
if (info.getKind() == RubyMixinElementInfo.K_EXTEND) {
String extKey = (String) info.getObject();
if (names.add(extKey)) {
if (/* !this.isMeta() && */!extKey
.endsWith(RubyMixin.INSTANCE_SUFFIX))
extKey += RubyMixin.INSTANCE_SUFFIX;
IRubyMixinElement element = model.createRubyElement(extKey);
if (element instanceof RubyMixinClass)
result.add((RubyMixinClass) element);
}
}
}
return result.toArray(new RubyMixinClass[result.size()]);
}
public void findMethods(IMixinSearchPattern pattern,
IMixinSearchRequestor requestor) {
findMethods(pattern, requestor, new HashSet<String>());
}
protected void findMethods(IMixinSearchPattern pattern,
IMixinSearchRequestor requestor, Set<String> processedKeys) {
if (!processedKeys.add(key)) {
return;
}
IMixinElement mixinElement = model.getRawModel().get(key);
if (mixinElement == null)
return;
final IMixinElement[] children = mixinElement.getChildren();
for (int i = 0; i < children.length; i++) {
final IMixinElement child = children[i];
if (pattern.evaluate(child.getLastKeySegment())) {
IRubyMixinElement element = model.createRubyElement(child);
if (element instanceof RubyMixinMethod) {
requestor.acceptResult(element);
} else if (element instanceof RubyMixinAlias) {
RubyMixinAlias alias = (RubyMixinAlias) element;
IRubyMixinElement oldElement = alias.getOldElement();
if (oldElement instanceof RubyMixinMethod) {
AliasedRubyMixinMethod a = new AliasedRubyMixinMethod(
model, alias);
requestor.acceptResult(a);
}
}
}
}
RubyMixinClass[] included = this.getIncluded();
for (int i = 0; i < included.length; i++) {
included[i].findMethods(pattern, requestor, processedKeys);
}
RubyMixinClass[] extended = this.getExtended();
for (int i = 0; i < extended.length; i++) {
extended[i].findMethods(pattern, requestor, processedKeys);
}
if (!this.key.endsWith(RubyMixin.VIRTUAL_SUFFIX)) {
RubyMixinClass superclass = getSuperclass();
if (superclass != null) {
if (!superclass.getKey().equals(key)) {
superclass.findMethods(pattern, requestor, processedKeys);
}
}
} else {
String stdKey = this.key.substring(0, key.length()
- RubyMixin.VIRTUAL_SUFFIX.length());
IRubyMixinElement realElement = model.createRubyElement(stdKey);
if (realElement instanceof RubyMixinClass) {
RubyMixinClass rubyMixinClass = (RubyMixinClass) realElement;
rubyMixinClass.findMethods(pattern, requestor, processedKeys);
}
}
}
public RubyMixinMethod[] findMethods(IMixinSearchPattern pattern) {
final List<RubyMixinMethod> result = new ArrayList<RubyMixinMethod>();
this.findMethods(pattern, new IMixinSearchRequestor() {
final Set<String> names = new HashSet<String>();
@Override
public void acceptResult(IRubyMixinElement element) {
if (element instanceof RubyMixinMethod) {
RubyMixinMethod method = (RubyMixinMethod) element;
if (names.add(method.getName())) {
result.add(method);
}
}
}
}, new HashSet<String>());
return result.toArray(new RubyMixinMethod[result.size()]);
}
public RubyMixinMethod getMethod(String name) {
return getMethod(name, new HashSet<String>());
}
protected RubyMixinMethod getMethod(String name, Set<String> processedKeys) {
if (!processedKeys.add(key)) {
return null;
}
String possibleKey = key + MixinModel.SEPARATOR + name;
IMixinElement mixinElement = model.getRawModel().get(possibleKey);
if (mixinElement != null) {
IRubyMixinElement element = model.createRubyElement(mixinElement);
if (element instanceof RubyMixinMethod) {
return (RubyMixinMethod) element;
}
if (element instanceof RubyMixinAlias) {
RubyMixinAlias alias = (RubyMixinAlias) element;
IRubyMixinElement oldElement = alias.getOldElement();
if (oldElement instanceof RubyMixinMethod) {
return new AliasedRubyMixinMethod(model, alias);
}
}
}
RubyMixinClass[] included = this.getIncluded();
for (int i = 0; i < included.length; i++) {
if (!this.key.equals(included[i].key)) {
RubyMixinMethod method = included[i].getMethod(name,
processedKeys);
if (method != null)
return method;
}
}
RubyMixinClass[] extended = this.getExtended();
for (int i = 0; i < extended.length; i++) {
RubyMixinMethod method = extended[i].getMethod(name, processedKeys);
if (method != null)
return method;
}
// search superclass
// if (!this.key.equals("Object") && !this.key.equals("Object%")) {
RubyMixinClass superclass = getSuperclass();
if (superclass != null) {
if (superclass.getKey().equals(key))
return null;
return superclass.getMethod(name, processedKeys);
}
// }
return null;
}
public RubyMixinClass[] getClasses() {
List<RubyMixinClass> result = new ArrayList<RubyMixinClass>();
IMixinElement mixinElement = model.getRawModel().get(key);
IMixinElement[] children = mixinElement.getChildren();
for (int i = 0; i < children.length; i++) {
IRubyMixinElement element = model.createRubyElement(children[i]);
if (element instanceof RubyMixinClass)
result.add((RubyMixinClass) element);
}
return result.toArray(new RubyMixinClass[result.size()]);
}
public RubyMixinVariable[] getFields() {
List<RubyMixinVariable> result = new ArrayList<RubyMixinVariable>();
IMixinElement mixinElement = model.getRawModel().get(key);
IMixinElement[] children = mixinElement.getChildren();
for (int i = 0; i < children.length; i++) {
IRubyMixinElement element = model.createRubyElement(children[i]);
if (element instanceof RubyMixinVariable)
result.add((RubyMixinVariable) element);
}
RubyMixinClass superclass = getSuperclass();
if (superclass != null && superclass.key != "Object" //$NON-NLS-1$
&& superclass.key != "Object%") { //$NON-NLS-1$
if (superclass.getKey().equals(key))
return null;
RubyMixinVariable[] superFields = superclass.getFields();
result.addAll(Arrays.asList(superFields));
}
return result.toArray(new RubyMixinVariable[result.size()]);
}
}