blob: 707a8ed0516c4f80ad95ebe56a59ef7f970aec80 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.core.sourcemodel;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ltk.model.core.LtkModelUtils;
import org.eclipse.statet.ltk.model.core.element.LtkModelElementFilter;
import org.eclipse.statet.r.core.model.RElementAccess;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.core.model.RFrame;
import org.eclipse.statet.r.core.model.RLangSourceElement;
import org.eclipse.statet.r.core.model.RSourceFrame;
@NonNullByDefault
abstract class BuildSourceFrame implements RSourceFrame {
static final int CREATED_NO= 0;
static final int CREATED_SEARCH= 1;
static final int CREATED_RESOLVED= 2;
static final int CREATED_EXPLICIT= 3;
static final int CREATED_IMPORTED= 4;
private static final ImList<BuildSourceFrame> NO_PARENTS= ImCollections.emptyList();
public static String createId(final int type, final @Nullable String name, final int alt) {
if (type == RFrame.PACKAGE && name != null) {
return "package:" + name; //$NON-NLS-1$
}
return (name != null) ?
Integer.toHexString(type) + ":`" + name + '`' : //$NON-NLS-1$
Integer.toHexString(type) + ":#" + Integer.toHexString(alt); //$NON-NLS-1$
}
static final class ElementAccessList {
private final @Nullable String name;
final List<ElementAccess> entries;
RFrame frame;
int isCreated;
public ElementAccessList(final @Nullable String name) {
this.name= name;
this.entries= new ArrayList<>(4);
this.isCreated= CREATED_NO;
}
public @Nullable String getName() {
return this.name;
}
public void postAdd(final ElementAccess access) {
access.shared= this;
this.entries.add(access);
access.fullNode.addAttachment(access);
}
public ImList<ElementAccess> getAll(final boolean includeSlaves) {
if (!includeSlaves) {
int counter= 0;
for (final ElementAccess element : this.entries) {
if (element.isSlave()) {
counter++;
}
}
if (counter > 0) {
final ElementAccess[] elements= new @NonNull ElementAccess[this.entries.size() - counter];
counter= 0;
for (final ElementAccess element : this.entries) {
if (element.isMaster()) {
elements[counter++]= element;
}
}
Arrays.sort(elements, RElementAccess.NAME_POSITION_COMPARATOR);
return ImCollections.newList(elements);
}
}
{ final ElementAccess[] elements= this.entries.toArray(new @NonNull ElementAccess[this.entries.size()]);
Arrays.sort(elements, RElementAccess.NAME_POSITION_COMPARATOR);
return ImCollections.newList(elements);
}
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder();
sb.append((this.name != null) ? this.name : "<null>");
sb.append(" (").append(this.entries.size()).append(')'); //$NON-NLS-1$
return sb.toString();
}
}
static class RunScope extends BuildSourceFrame {
public RunScope(final int type, final String id, final BuildSourceFrame parent) {
super(type, id, new BuildSourceFrame[] { parent });
}
@Override
public @Nullable RElementName getElementName() {
return null;
}
@Override
public void add(final @Nullable String name, final ElementAccess access) {
this.parents.get(0).add(name, access);
}
@Override
public void addLateResolve(final @Nullable String name, final ElementAccess access) {
this.parents.get(0).addLateResolve(name, access);
}
@Override
public void addRunResolve(final @Nullable String name, final ElementAccess access) {
ElementAccessList detail= this.data.get(name);
if (detail == null) {
detail= new ElementAccessList(name);
detail.frame= this;
this.data.put(name, detail);
}
detail.entries.add(access);
if (access.isWriteAccess() && !access.isDeletion()) {
detail.isCreated= CREATED_EXPLICIT;
}
else if (access.isImport()) {
detail.isCreated= CREATED_IMPORTED;
}
access.shared= detail;
access.fullNode.addAttachment(access);
}
@Override
void addClass(final @Nullable String name, final ElementAccess access) {
this.parents.get(0).addClass(name, access);
}
@Override
void runLateResolve(final boolean onlyWrite) {
}
}
static class DefScope extends BuildSourceFrame {
private Map<String, ElementAccessList> lateWrite;
private Map<String, ElementAccessList> lateRead;
private @Nullable Map<@Nullable String, ElementAccessList> classes;
private @Nullable RElementName elementName;
DefScope(final int type, final String id, final @Nullable String name,
final BuildSourceFrame @Nullable [] parents) {
super(type, id, parents);
this.lateWrite= new HashMap<>();
this.lateRead= new HashMap<>();
switch (type) {
case PROJECT:
this.elementName= null;
// this.elementName= RElementName.create(RElementName.SCOPE_SEARCH_ENV, ".GlobalEnv");
this.classes= new HashMap<>();
break;
case PACKAGE:
this.elementName= RElementName.create(RElementName.SCOPE_PACKAGE, name);
this.classes= new HashMap<>();
break;
default:
this.classes= null;
}
}
@Override
public @Nullable RElementName getElementName() {
return this.elementName;
}
@Override
void add(final @Nullable String name, final ElementAccess access) {
ElementAccessList detail= this.data.get(name);
if (detail == null) {
detail= new ElementAccessList(name);
detail.frame= this;
this.data.put(name, detail);
}
detail.entries.add(access);
if (access.isWriteAccess() && !access.isDeletion()) {
detail.isCreated= CREATED_EXPLICIT;
}
else if (access.isImport()) {
detail.isCreated= CREATED_IMPORTED;
}
access.shared= detail;
access.fullNode.addAttachment(access);
}
@Override
void addLateResolve(final @Nullable String name, final ElementAccess access) {
if (name == null) {
add(null, access);
return;
}
ElementAccessList detail= this.data.get(name);
if (detail != null && detail.isCreated <= CREATED_NO) {
detail= null;
}
if (detail == null) {
final Map<String, ElementAccessList> late=
((access.flags & (0xf | ElementAccess.A_SUB)) == ElementAccess.A_WRITE) ?
this.lateWrite : this.lateRead;
detail= late.get(name);
if (detail == null) {
detail= new ElementAccessList(name);
late.put(name, detail);
}
}
detail.entries.add(access);
access.shared= detail;
access.fullNode.addAttachment(access);
}
@Override
void addClass(final @Nullable String name, final ElementAccess access) {
final Map<@Nullable String, ElementAccessList> classes= this.classes;
if (classes == null) {
this.parents.get(0).addClass(name, access);
return;
}
ElementAccessList detail= classes.get(name);
if (detail == null) {
detail= new ElementAccessList(name);
detail.frame= this;
classes.put(name, detail);
}
detail.entries.add(access);
access.shared= detail;
access.fullNode.addAttachment(access);
}
@Override
void addRunResolve(final @Nullable String name, final ElementAccess access) {
}
@Override
void runLateResolve(final boolean onlyWrite) {
final BuildSourceFrame[] searchList= createSearchList();
Map<String, ElementAccessList> map= this.lateWrite;
if (map != null) {
final BuildSourceFrame defaultScope= this;
ITER_NAMES : for (final ElementAccessList detail : map.values()) {
for (int requiredCreation= CREATED_SEARCH; requiredCreation >= 0; requiredCreation--) {
for (int i= 0; i < searchList.length; i++) {
final ElementAccessList exist= searchList[i].data.get(detail.getName());
if (exist != null && exist.isCreated >= requiredCreation) {
for (final ElementAccess access : detail.entries) {
access.shared= exist;
}
exist.entries.addAll(detail.entries);
continue ITER_NAMES;
}
}
}
detail.frame= defaultScope;
detail.isCreated= CREATED_SEARCH;
this.data.put(detail.getName(), detail);
continue ITER_NAMES;
}
this.lateWrite= null;
}
if (onlyWrite) {
return;
}
map= this.lateRead;
if (map != null) {
BuildSourceFrame defaultScope= this;
for (int i= 0; i < searchList.length; i++) {
if (searchList[i].type <= RFrame.PACKAGE) { // package or project
defaultScope= searchList[i];
break;
}
}
ITER_NAMES : for (final ElementAccessList detail : map.values()) {
for (int requiredCreation= CREATED_SEARCH; requiredCreation >= 0; requiredCreation--) {
for (int i= 0; i < searchList.length; i++) {
final ElementAccessList exist= searchList[i].data.get(detail.getName());
if (exist != null && exist.isCreated >= requiredCreation) {
for (final ElementAccess access : detail.entries) {
access.shared= exist;
}
exist.entries.addAll(detail.entries);
continue ITER_NAMES;
}
}
}
detail.frame= defaultScope;
defaultScope.data.put(detail.getName(), detail);
continue ITER_NAMES;
}
this.lateRead= null;
}
}
}
protected final Map<@Nullable String, ElementAccessList> data;
protected final int type;
protected final String id;
ImList<BuildSourceFrame> parents;
private List<BuildSourceFrameElement> elements= Collections.emptyList();
private @Nullable WeakReference<List<RLangSourceElement>> modelChildren;
BuildSourceFrame(final int type, final String id, final BuildSourceFrame @Nullable [] parents) {
this.type= type;
this.id= id;
if (parents != null) {
this.parents= ImCollections.newList(parents);
}
else {
this.parents= NO_PARENTS;
}
this.data= new HashMap<>();
}
void addFrameElement(final BuildSourceFrameElement element) {
final int length= this.elements.size();
final BuildSourceFrameElement[] elements= this.elements.toArray(new @NonNull BuildSourceFrameElement[length + 1]);
elements[length]= element;
this.elements= ImCollections.newList(elements);
}
abstract void add(final @Nullable String name, final ElementAccess access);
abstract void addLateResolve(final @Nullable String name, final ElementAccess access);
abstract void addRunResolve(final @Nullable String name, final ElementAccess access);
abstract void addClass(final @Nullable String name, final ElementAccess access);
abstract void runLateResolve(final boolean onlyWrite);
protected BuildSourceFrame[] createSearchList() {
final ArrayList<BuildSourceFrame> list= new ArrayList<>();
int idx= 0;
list.add(this);
while (idx < list.size()) {
final List<BuildSourceFrame> ps= list.get(idx++).parents;
for (final BuildSourceFrame p : ps) {
if (!list.contains(p)) {
list.add(p);
}
}
}
return list.toArray(new @NonNull BuildSourceFrame[list.size()]);
}
@Override
public int getFrameType() {
return this.type;
}
@Override
public String getFrameId() {
return this.id;
}
@Override
public List<? extends RSourceFrame> getPotentialParents() {
return this.parents;
}
@Override
public Set<@Nullable String> getAllAccessNames() {
return Collections.unmodifiableSet(this.data.keySet());
}
@Override
public ImList<? extends RElementAccess> getAllAccessOf(final @Nullable String name, final boolean includeSlaves) {
final ElementAccessList list= this.data.get(name);
if (list == null) {
return ImCollections.emptyList();
}
return list.getAll(includeSlaves);
}
@Override
public boolean isResolved(final String name) {
final ElementAccessList accessList= this.data.get(name);
return (accessList != null && accessList.isCreated >= CREATED_RESOLVED);
}
@Override
public List<? extends RLangSourceElement> getModelElements() {
return this.elements;
}
@Override
public boolean hasModelChildren(final @Nullable LtkModelElementFilter<? super RLangSourceElement> filter) {
for (final ElementAccessList list : this.data.values()) {
for (final ElementAccess access : list.entries) {
final RLangSourceElement element= access.modelElement;
if (element != null
&& (filter == null || filter.include(element)) ) {
return true;
}
}
}
return false;
}
@Override
public List<? extends RLangSourceElement> getModelChildren(final @Nullable LtkModelElementFilter<? super RLangSourceElement> filter) {
if (this.data.isEmpty()) {
return RSourceModel.NO_R_SOURCE_CHILDREN;
}
final WeakReference<List<RLangSourceElement>> childrenRef= this.modelChildren;
List<RLangSourceElement> children= (childrenRef != null) ? childrenRef.get() : null;
if (children != null) {
if (filter == null) {
return children;
}
else {
return LtkModelUtils.<RLangSourceElement>getChildren(children, filter);
}
}
else {
children= new ArrayList<>();
for (final ElementAccessList list : this.data.values()) {
for (final ElementAccess access : list.entries) {
final RLangSourceElement element= access.modelElement;
if (element != null
&& (filter == null || filter.include(element)) ) {
children.add(element);
}
}
}
children= Collections.unmodifiableList(children);
if (filter == null) {
this.modelChildren= new WeakReference<>(children);
}
return children;
}
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder(getClass().getSimpleName());
final RElementName elementName= getElementName();
if (elementName != null) {
sb.append(' ').append(elementName);
}
else {
sb.append(" <unnamed>"); //$NON-NLS-1$
}
return sb.toString();
}
}