package bluej.stride.framedjava.elements;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import bluej.debugger.gentype.ConstructorReflective;
import bluej.editor.moe.ScopeColors;
import bluej.parser.AssistContent.CompletionKind;
import bluej.parser.AssistContent.ParamInfo;
import bluej.parser.entity.PackageResolver;
import bluej.stride.framedjava.ast.Parser;
import bluej.stride.framedjava.ast.SlotFragment;
import bluej.stride.framedjava.errors.CodeError;
import bluej.stride.framedjava.errors.SyntaxCodeError;
import bluej.stride.generic.AssistContentThreadSafe;
import bluej.stride.generic.InteractionManager;
import nu.xom.Attribute;
import nu.xom.Element;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.debugger.gentype.GenTypeClass;
import bluej.debugger.gentype.JavaType;
import bluej.debugger.gentype.MethodReflective;
import bluej.debugger.gentype.Reflective;
import bluej.editor.moe.MoeSyntaxDocument;
import bluej.parser.ExpressionTypeInfo;
import bluej.parser.entity.EntityResolver;
import bluej.parser.entity.ParsedReflective;
import bluej.parser.nodes.JavaParentNode;
import bluej.parser.nodes.ParsedNode;
import bluej.parser.nodes.ParsedTypeNode;
import bluej.stride.framedjava.ast.FrameFragment;
import bluej.stride.framedjava.ast.JavaFragment;
import bluej.stride.framedjava.ast.JavaFragment.PosInSourceDoc;
import bluej.stride.framedjava.ast.JavaSource;
import bluej.stride.framedjava.ast.JavadocUnit;
import bluej.stride.framedjava.ast.NameDefSlotFragment;
import bluej.stride.framedjava.ast.TypeSlotFragment;
import bluej.stride.framedjava.errors.ErrorShower;
import bluej.stride.framedjava.frames.ClassFrame;
import bluej.stride.framedjava.frames.ConstructorFrame;
import bluej.stride.framedjava.slots.ExpressionSlot;
import bluej.stride.generic.Frame.ShowReason;
import bluej.utility.Utility;
| A code element corresponding to a top-level class.
|
public class ClassElement
extends DocumentContainerCodeElement implements TopLevelCodeElement{
public static final String ELEMENT = "class";
| The name of this class
|
private final NameDefSlotFragment className;
| The type we extend (null if none)
|
private final TypeSlotFragment extendsName;
| The list of types we implement (empty if none)
|
private final List<TypeSlotFragment> implementsList;
| The package name (will not be null, but package name within may be blank
|
private final String packageName;
| The list of imports in this class
|
private final List<ImportElement> imports;
| The list of fields in this class
|
private final List<CodeElement> fields;
| The list of constructors for this class
|
private final List<CodeElement> constructors;
| The list of methods in this class
|
private final List<CodeElement> methods;
| The resolver used by the project this class lives in (cached for convenience)
|
private final EntityResolver projectResolver;
| Is this class abstract (true) or not (false)?
|
private boolean abstractModifier = false;
| The documentation for this class
|
private JavadocUnit documentation;
| The frame corresponding to this code element (may be null)
|
private ClassFrame frame;
| The curly brackets and class keyword in the generated code (saved for mapping positions)
|
private final FrameFragment openingCurly;
private final FrameFragment closingCurly;
private JavaFragment classKeyword;
| The generated Java code for this class, used for doing code completion without
| needing to always regenerate the document.
|
private DocAndPositions sourceDocument;
private ExpressionSlot<?> sourceDocumentCompleting;
| A map of documents for given contents. This guards against race hazards, so
| that we use the correct document for the given content, even when we are hopping
| across threads and potentially generating several documents in a short space
| of time, concurrent with looking up information in them.
|
| This cache does not have a size limit, but that shouldn't matter as it is per-instance
| so the only potential differences in source code are down to which slot is being completed,
| giving a limit on the number of documents we could generate for a given source version
| (each ClassElement is immutable).
|
private final HashMap<String, DocAndPositions> documentCache = new HashMap<>();
| Creates a class element from the given frame (when generating code elements for
| analysis/compilation from the open editor)
|
public ClassElement(ClassFrame frame, EntityResolver projectResolver, boolean abstractModifier, NameDefSlotFragment className,
TypeSlotFragment extendsName, List<TypeSlotFragment> implementsList, List<? extends CodeElement> fields,
List<? extends CodeElement> constructors, List<? extends CodeElement> methods, JavadocUnit documentation,
String packageName, List<ImportElement> imports, boolean enabled)
{
this.frame = frame;
this.openingCurly = new FrameFragment(this.frame, this, "{");
this.closingCurly = new FrameFragment(this.frame, this, "}");
this.abstractModifier = abstractModifier;
this.className = className;
this.extendsName = extendsName;
this.documentation = documentation;
this.packageName = (packageName == null) ? "" : packageName;
this.imports = new LinkedList<>(imports);
this.implementsList = new ArrayList<>(implementsList);
this.fields = new ArrayList<>(fields);
this.fields.forEach(field -> field.setParent(this));
this.constructors = new ArrayList<>(constructors);
this.constructors.forEach(constructor -> constructor.setParent(this));
this.methods = new ArrayList<>(methods);
this.methods.forEach(method -> method.setParent(this));
this.enable = enabled;
this.documentation = documentation;
if (this.documentation == null) {
this.documentation = new JavadocUnit("");
}
this.projectResolver = projectResolver;
}
| Creates a class element from the given XML element, used when loading code
| from disk or from the clipboard.
|
public ClassElement(Element el, EntityResolver projectResolver, String packageName)
{
Attribute abstractAttribute = el.getAttribute("abstract");
abstractModifier = (abstractAttribute == null) ? false : Boolean.valueOf(abstractAttribute.getValue());
className = new NameDefSlotFragment(el.getAttributeValue("name"));
final String extendsAttribute = el.getAttributeValue("extends");
extendsName = (extendsAttribute != null) ? new TypeSlotFragment(extendsAttribute, el.getAttributeValue("extends-java")) : null;
this.packageName = packageName;
Element javadocEL = el.getFirstChildElement("javadoc");
if (javadocEL != null) {
documentation = new JavadocUnit(javadocEL);
}
if (documentation == null) {
documentation = new JavadocUnit("");
}
implementsList = TopLevelCodeElement.xmlToTypeList(el, "implements", "implementstype", "type");
imports = Utility.mapList(TopLevelCodeElement.fillChildrenElements(this, el, "imports"), e -> (ImportElement)e);
fields = TopLevelCodeElement.fillChildrenElements(this, el, "fields");
constructors = TopLevelCodeElement.fillChildrenElements(this, el, "constructors");
methods = TopLevelCodeElement.fillChildrenElements(this, el, "methods");
enable = Boolean.valueOf(el.getAttributeValue("enable"));
this.projectResolver = projectResolver;
this.openingCurly = new FrameFragment(null, this, "{");
this.closingCurly = new FrameFragment(null, this, "}");
}
| Creates a class element with minimum information (when creating new class from template name)
|
public ClassElement(EntityResolver entityResolver, boolean abstractModifier, String className, String packageName,
List<? extends CodeElement> constructors)
{
this(null, entityResolver, abstractModifier, new NameDefSlotFragment(className), null,
Collections.emptyList(), Collections.emptyList(), constructors, Collections.emptyList(),
null, packageName, Collections.emptyList(), true);
}
@Override
@OnThread(Tag.FXPlatform)
public JavaSource toJavaSource()
{
return getDAP(null).java;
}
@OnThread(Tag.FXPlatform)
private JavaSource generateJavaSource()
{
List<JavaFragment> header = new ArrayList<>();
header.add(new FrameFragment(frame, this, "public "));
if (abstractModifier) {
header.add(f(frame, "abstract "));
}
classKeyword = new FrameFragment(frame, this, "class ");
Collections.addAll(header, classKeyword, className);
if (extendsName != null && !extendsName.isEmpty()) {
Collections.addAll(header, space(), f(frame, "extends"), space(), extendsName);
}
if (!implementsList.isEmpty())
{
header.addAll(Arrays.asList(space(), f(frame, "implements"), space()));
header.addAll(implementsList.stream().collect(Utility.intersperse(() -> f(null, ", "))));
}
JavaSource java = new JavaSource(null, header);
java.prependJavadoc(documentation.getJavaCode());
java.prependLine(Arrays.asList((JavaFragment) f(frame, "")), null);
Utility.backwards(CodeElement.toJavaCodes(imports)).forEach(imp -> java.prepend(imp));
java.prependLine(Collections.singletonList(f(frame, "import lang.stride.*;")), null);
if (!packageName.equals(""))
java.prependLine(Arrays.asList(f(frame, "package " + packageName + ";")), null);
openingCurly.setFrame(frame);
java.appendLine(Arrays.asList(openingCurly), null);
fields.stream().filter(f -> f.isEnable()).forEach(f -> java.addIndented(f.toJavaSource()));
constructors.stream().filter(c -> c.isEnable()).forEach(c -> {
java.appendLine(Arrays.asList((JavaFragment) f(frame, "")), null);
java.addIndented(c.toJavaSource());
});
methods.stream().filter(m -> m.isEnable()).forEach(m -> {
java.appendLine(Arrays.asList((JavaFragment) f(frame, "")), null);
java.addIndented(m.toJavaSource());
});
closingCurly.setFrame(frame);
java.appendLine(Arrays.asList(closingCurly), null);
return java;
}
@Override
public LocatableElement toXML()
{
LocatableElement classEl = new LocatableElement(this, ELEMENT);
if (abstractModifier) {
classEl.addAttribute(new Attribute("abstract", String.valueOf(abstractModifier)));
}
classEl.addAttributeCode("name", className);
if (extendsName != null) {
classEl.addAttributeStructured("extends", extendsName);
}
addEnableAttribute(classEl);
if (documentation != null) {
classEl.appendChild(documentation.toXML());
}
appendCollection(classEl, imports, "imports");
classEl.appendChild(TopLevelCodeElement.typeListToXML(implementsList, "implements", "implementstype", "type"));
appendCollection(classEl, fields, "fields");
appendCollection(classEl, constructors, "constructors");
appendCollection(classEl, methods, "methods");
classEl.addAttribute(TopLevelCodeElement.getStrideVersionAttribute());
return classEl;
}
private void appendCollection(Element topEl, List<? extends CodeElement> collection, String name)
{
Element collectionEl = new Element(name);
collection.forEach(element -> collectionEl.appendChild(element.toXML()));
topEl.appendChild(collectionEl);
}
@Override
public ClassFrame createFrame(InteractionManager editor)
{
frame = new ClassFrame(editor, projectResolver, packageName, imports, documentation, abstractModifier, className, extendsName, implementsList, isEnable());
fields.forEach(member -> frame.getfieldsCanvas().insertBlockAfter(member.createFrame(editor), null));
constructors.forEach(member -> frame.getConstructorsCanvas().insertBlockAfter(member.createFrame(editor), null));
methods.forEach(member -> frame.getMethodsCanvas().insertBlockAfter(member.createFrame(editor), null));
return frame;
}
@Override
public ClassFrame createTopLevelFrame(InteractionManager editor)
{
return createFrame(editor);
}
public String getName()
{
return className.getContent();
}
@Override
public List childrenUpTo(CodeElement c)
{
List<CodeElement> joined = new ArrayList<>();
joined.addAll(fields);
joined.addAll(constructors);
joined.addAll(methods);
return joined.subList(0, joined.indexOf(c));
}
public JavaFragment getNameElement(ConstructorFrame frame)
{
return new JavaFragment() {
@Override
protected String getJavaCode(Destination dest, ExpressionSlot<?> completing, Parser.DummyNameGenerator dummyNameGenerator)
{
return getName();
}
@Override
public ErrorShower getErrorShower()
{
return frame;
}
@Override
protected JavaFragment getCompileErrorRedirect()
{
return null;
}
@Override
public Stream findEarlyErrors()
{
return Stream.empty();
}
@Override
public void addError(CodeError codeError)
{
frame.addError(codeError);
}
};
}
@Override
public ClassElement getTopLevelElement()
{
return this;
}
public ClassFrame getFrame()
{
return frame;
}
public PosInSourceDoc getPosInsideClass()
{
return openingCurly.getPosInSourceDoc(+1);
}
@Override
public List getImports()
{
return Collections.unmodifiableList(imports);
}
@Override
@OnThread(Tag.FXPlatform)
public ExpressionTypeInfo getCodeSuggestions(PosInSourceDoc pos, ExpressionSlot<?> completing)
{
MoeSyntaxDocument doc = getSourceDocument(completing);
Optional<Integer> resolvedPos = resolvePos(doc, pos);
return resolvedPos.map(rpos -> doc.getParser().getExpressionType(rpos, getSourceDocument(completing)))
.orElse(null);
}
@OnThread(Tag.FXPlatform)
private Optional resolvePos(MoeSyntaxDocument doc, PosInSourceDoc pos)
{
DocAndPositions docAndPositions = documentCache.get(doc.getText(0, doc.getLength()));
Optional<Integer> resolvedPos = Optional.ofNullable(docAndPositions.fragmentPositions.get(pos.getFragment()));
return resolvedPos.map(p -> p + pos.offset);
}
@Override
public String getStylePrefix()
{
return "class-";
}
@Override
@OnThread(Tag.FXPlatform)
public EntityResolver getResolver()
{
return getSourceDocument(null).getParser();
}
@Override
public InteractionManager getEditor()
{
return getFrame().getEditor();
}
@Override
public void show(ShowReason reason)
{
frame.show(reason);
}
@OnThread(Tag.FXPlatform)
private MoeSyntaxDocument getSourceDocument(ExpressionSlot completing)
{
return getDAP(completing).getDocument(projectResolver);
}
@OnThread(Tag.FXPlatform)
private synchronized DocAndPositions getDAP(ExpressionSlot completing)
{
if (sourceDocument == null || sourceDocumentCompleting != completing)
{
IdentityHashMap<JavaFragment, Integer> positions = new IdentityHashMap<>();
sourceDocumentCompleting = completing;
JavaSource java = generateJavaSource();
String src = java.toMemoryJavaCodeString(positions, completing);
if (documentCache.containsKey(src))
{
sourceDocument = documentCache.get(src);
sourceDocument.fragmentPositions.putAll(positions);
}
else
{
sourceDocument = new DocAndPositions(src, java, positions);
documentCache.put(src, sourceDocument);
}
}
return sourceDocument;
}
@Override
public Stream streamContained()
{
Stream<CodeElement> result = streamContained(fields);
result = Stream.concat(result, streamContained(constructors));
return Stream.concat(result, streamContained(methods));
}
@Override
protected Stream getDirectSlotFragments()
{
return Stream.<SlotFragment>of(className, extendsName).filter(s -> s != null);
}
public Stream streamMethods()
{
return methods.stream();
}
@OnThread(Tag.FXPlatform)
public Reflective qualifyType(String name, PosInSourceDoc pos)
{
final MoeSyntaxDocument doc = getSourceDocument(null);
final Optional<Integer> rpos = resolvePos(doc, pos);
if (!rpos.isPresent())
return null;
ParsedNode node = doc.getParser().findNodeAtOrAfter(rpos.get(), 0).getNode();
if (node instanceof JavaParentNode)
{
JavaParentNode jNode = (JavaParentNode)node;
JavaType t = jNode.resolvePackageOrClass(name, getClassNode()).getType();
if (t instanceof GenTypeClass)
return ((GenTypeClass)t).getReflective();
}
return null;
}
@OnThread(Tag.FXPlatform)
private Reflective getClassNode()
{
MoeSyntaxDocument doc = getSourceDocument(null);
ParsedNode node = doc.getParser().findNodeAtOrAfter(resolvePos(doc, classKeyword.getPosInSourceDoc()).get(), 0).getNode();
if (node instanceof ParsedTypeNode)
{
return new ParsedReflective((ParsedTypeNode)node);
}
return null;
}
@OnThread(Tag.FXPlatform)
public Reflective findSuperMethod(String name, List<String> qualParamTypes)
{
if (classKeyword == null)
return null;
String superClass;
if (extendsName == null || extendsName.getContent().isEmpty())
superClass = "Object";
else{ superClass = extendsName.getContent();
}
getSourceDocument(null);
Reflective qualSuper = qualifyType(superClass, classKeyword.getPosInSourceDoc());
while (qualSuper != null)
{
Set<MethodReflective> overloads = qualSuper.getDeclaredMethods().get(name);
if (overloads != null && overloads.stream().anyMatch(m -> qualParamTypes.equals(Utility.mapList(m.getParamTypes(), t -> t.toString(false)))))
return qualSuper;
qualSuper = qualSuper.getSuperTypesR().stream().filter(r -> !r.isInterface()).findFirst().orElse(null);
}
return null;
}
@Override
@OnThread(Tag.FXPlatform)
public void updateSourcePositions()
{
getSourceDocument(null);
}
public boolean isAbstract()
{
return abstractModifier;
}
public String getExtends()
{
return extendsName != null ? extendsName.getContent() : null;
}
public List getImplements()
{
return Utility.mapList(implementsList, TypeSlotFragment::getContent);
}
public List extends CodeElement> getMethods()
{
return methods;
}
public List extends CodeElement> getFields()
{
return fields;
}
public List extends CodeElement> getConstructors()
{
return constructors;
}
@OnThread(Tag.FXPlatform)
@Override
public List getSuperConstructors()
{
if (extendsName == null || extendsName.getContent().isEmpty() || classKeyword == null)
{
return Collections.emptyList();
}
getSourceDocument(null);
Reflective qualSuper = qualifyType(extendsName.getContent(), classKeyword.getPosInSourceDoc());
if (qualSuper != null)
{
return qualSuper.getDeclaredConstructors();
}
return Collections.emptyList();
}
@Override
public List getThisConstructors()
{
return constructors.stream()
.filter(c -> c instanceof ConstructorElement)
.map(c -> (ConstructorElement)c)
.map(c -> {
List<ParamInfo> paramInfo = Utility.mapList(c.getParams(), p -> new ParamInfo(p.getParamType().getContent(), p.getParamName().getContent(), "", () -> ""));
return new AssistContentThreadSafe(c.getAccessPermission().asAccess(), getName(), c.getDocumentation(), CompletionKind.CONSTRUCTOR, getName(), null, paramInfo, null, null, null);
})
.collect(Collectors.toList());
}
private static class DocAndPositions
{
public final JavaSource java;
public final IdentityHashMap<JavaFragment, Integer> fragmentPositions;
private String src;
private MoeSyntaxDocument document;
public DocAndPositions(String src, JavaSource java, IdentityHashMap<JavaFragment, Integer> fragmentPositions)
{
this.src = src;
this.java = java;
this.fragmentPositions = fragmentPositions;
}
@OnThread(Tag.FXPlatform)
public MoeSyntaxDocument getDocument(EntityResolver projectResolver)
{
if (document == null)
{
document = new MoeSyntaxDocument(projectResolver);
document.insertString(0, src);
document.enableParser(true);
}
return document;
}
}
@Override
@OnThread(Tag.FXPlatform)
public Stream findEarlyErrors()
{
return findEarlyErrors(toXML().buildLocationMap());
}
}
top,
use,
map,
class ClassElement
. ClassElement
. ClassElement
. ClassElement
. toJavaSource
. generateJavaSource
. toXML
. appendCollection
. createFrame
. createTopLevelFrame
. getName
. childrenUpTo
. getNameElement
. getJavaCode
. getErrorShower
. getCompileErrorRedirect
. findEarlyErrors
. addError
. getTopLevelElement
. getFrame
. getPosInsideClass
. getImports
. getCodeSuggestions
. resolvePos
. getStylePrefix
. getResolver
. getEditor
. show
. getSourceDocument
. getDAP
. streamContained
. getDirectSlotFragments
. streamMethods
. qualifyType
. getClassNode
. findSuperMethod
. updateSourcePositions
. isAbstract
. getExtends
. getImplements
. getMethods
. getFields
. getConstructors
. getSuperConstructors
. getThisConstructors
top,
use,
map,
class DocAndPositions
. DocAndPositions
. getDocument
. findEarlyErrors
773 neLoCode
+ 29 LoComm