/*******************************************************************************
* Copyright (c) 2005 Vlad Dumitrescu and others. All rights reserved. This program and
* the accompanying materials are made available under the terms of the Eclipse Public
* License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: Vlad Dumitrescu
*******************************************************************************/
package org.erlide.engine.internal.model.root;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.internal.model.SourceRange;
import org.erlide.engine.internal.util.ModelConfig;
import org.erlide.engine.model.ErlElementKind;
import org.erlide.engine.model.ErlModelException;
import org.erlide.engine.model.IErlElement;
import org.erlide.engine.model.IParent;
import org.erlide.engine.model.erlang.ErlangFunction;
import org.erlide.engine.model.erlang.ErlangIncludeFile;
import org.erlide.engine.model.erlang.IErlAttribute;
import org.erlide.engine.model.erlang.IErlComment;
import org.erlide.engine.model.erlang.IErlExport;
import org.erlide.engine.model.erlang.IErlFunction;
import org.erlide.engine.model.erlang.IErlImport;
import org.erlide.engine.model.erlang.IErlMember;
import org.erlide.engine.model.erlang.IErlPreprocessorDef;
import org.erlide.engine.model.erlang.IErlTypespec;
import org.erlide.engine.model.erlang.ISourceRange;
import org.erlide.engine.model.erlang.ISourceReference;
import org.erlide.engine.model.erlang.SourceKind;
import org.erlide.engine.model.root.IErlFolder;
import org.erlide.engine.model.root.IErlModel;
import org.erlide.engine.model.root.IErlModule;
import org.erlide.engine.model.root.IErlProject;
import org.erlide.engine.model.root.ISourceUnit;
import org.erlide.engine.services.parsing.ParserService;
import org.erlide.engine.services.parsing.ScannerService;
import org.erlide.engine.services.search.ModelUtilService;
import org.erlide.util.ErlLogger;
import org.erlide.util.SystemConfiguration;
import org.erlide.util.Util;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
public class ErlModule extends Openable implements IErlModule {
private static final OtpErlangAtom EXPORT_ALL = new OtpErlangAtom("export_all");
private static final boolean logging = false;
private IFile file;
private final SourceKind moduleKind;
protected String path;
private String initialText;
private boolean parsed;
private final String scannerName;
private final Collection<IErlComment> comments;
private ScannerService scanner;
private final String encoding;
private final ModelUtilService modelUtilService;
public ErlModule(final IParent parent, final String name, final IFile file) {
this(parent, name, file, null, null, null);
}
public ErlModule(final IParent parent, final String name, final String path,
final String encoding, final String initialText) {
this(parent, name, null, path, encoding, initialText);
}
private ErlModule(final IParent parent, final String name, final IFile file,
final String path, final String encoding, final String initialText) {
super(parent, name);
modelUtilService = ErlangEngine.getInstance().getModelUtilService();
this.file = file;
this.path = path;
this.encoding = encoding;
this.initialText = initialText;
moduleKind = SourceKind.nameToModuleKind(name);
parsed = false;
scannerName = createScannerName();
comments = Lists.newArrayList();
if (ModelConfig.verbose) {
// final IErlElement element = (IErlElement) parent;
// final String parentName = element.getName();
// ErlLogger.debug(
// "...creating " + parentName + "/" + getName() + " " + moduleKind);
}
}
public boolean internalBuildStructure(final IProgressMonitor pm) {
setChildren(null);
final String text = getInitialText();
if (text != null) {
final ParserService parser = ErlangEngine.getInstance().getParserService();
parsed = parser.parse(this, scannerName, !parsed, getFilePath(), text, true);
return parsed;
}
return true;
}
private String getInitialText() {
String charset;
if (initialText == null) {
if (file != null) {
if (file.isAccessible() && file.isSynchronized(0)) {
try {
charset = file.getCharset();
initialText = Util.getInputStreamAsString(file.getContents(),
charset);
} catch (final CoreException e) {
ErlLogger.warn(e);
}
}
} else if (path != null) {
try {
if (encoding != null) {
charset = encoding;
} else {
charset = modelUtilService.getProject(this).getWorkspaceProject()
.getDefaultCharset();
}
try (final FileInputStream is = new FileInputStream(new File(path))) {
initialText = Util.getInputStreamAsString(is, charset);
} catch (final IOException e) {
// ignore
}
} catch (final CoreException e) {
ErlLogger.warn(e);
}
}
}
return initialText;
}
@Override
public synchronized boolean buildStructure(final IProgressMonitor pm)
throws ErlModelException {
if (internalBuildStructure(pm)) {
final IErlModel model = ErlangEngine.getInstance().getModel();
if (model != null) {
model.notifyChange(this);
}
return true;
}
return false;
}
@Override
public String getFilePath() {
if (file != null) {
final IPath location = file.getLocation();
if (location != null) {
return location.toString();
}
}
return path;
}
@Override
public IErlElement getElementAt(final int position) throws ErlModelException {
return ErlangEngine.getInstance().getModel().innermostThat(this,
new Predicate<IErlElement>() {
@Override
public boolean apply(final IErlElement e) {
if (e instanceof ISourceReference) {
final ISourceReference ch = (ISourceReference) e;
ISourceRange r;
r = ch.getSourceRange();
if (r != null && r.hasPosition(position)) {
return true;
}
}
return false;
}
});
}
@Override
public IErlMember getElementAtLine(final int lineNumber) {
return (IErlMember) ErlangEngine.getInstance().getModel().innermostThat(this,
new Predicate<IErlElement>() {
@Override
public boolean apply(final IErlElement e) {
if (e instanceof ISourceReference) {
final ISourceReference sr = (ISourceReference) e;
if (sr.getLineStart() <= lineNumber
&& sr.getLineEnd() >= lineNumber) {
return true;
}
}
return false;
}
});
}
@Override
public SourceKind getSourceKind() {
return moduleKind;
}
@Override
public IResource getResource() {
return getCorrespondingResource();
}
@Override
public IResource getCorrespondingResource() {
return file;
}
public ISourceRange getSourceRange() {
return new SourceRange(0, 0);
}
@Override
protected boolean hasBuffer() {
return true;
}
// public void addComment(final IErlComment c) {
// comments.add(c);
// }
@Override
public void setComments(final Collection<? extends IErlComment> comments) {
synchronized (getModelLock()) {
this.comments.clear();
if (comments != null) {
this.comments.addAll(comments);
}
}
}
@Override
public Collection<IErlComment> getComments() {
synchronized (getModelLock()) {
return Collections.unmodifiableCollection(comments);
}
}
@Override
public IErlImport findImport(final ErlangFunction function) {
try {
for (final IErlElement e : getChildrenOfKind(ErlElementKind.IMPORT)) {
if (e instanceof IErlImport) {
final IErlImport ei = (IErlImport) e;
if (ei.hasFunction(function)) {
return ei;
}
}
}
} catch (final ErlModelException e) {
}
return null;
}
public IErlExport findExport(final ErlangFunction function) {
try {
for (final IErlElement e : getChildrenOfKind(ErlElementKind.EXPORT)) {
if (e instanceof IErlExport) {
final IErlExport ee = (IErlExport) e;
if (ee.hasFunction(function)) {
return ee;
}
}
}
} catch (final ErlModelException e) {
}
return null;
}
@Override
public IErlFunction findFunction(final ErlangFunction function) {
try {
for (final IErlElement fun : getChildrenOfKind(ErlElementKind.FUNCTION)) {
if (fun instanceof IErlFunction) {
final IErlFunction f = (IErlFunction) fun;
if (f.getName().equals(function.name)
&& (function.arity < 0 || f.getArity() == function.arity)) {
return f;
}
}
}
} catch (final ErlModelException e) { // ignore
}
return null;
}
@Override
public IErlTypespec findTypespec(final String typeName) {
try {
for (final IErlElement child : getChildrenOfKind(ErlElementKind.TYPESPEC)) {
if (child instanceof IErlTypespec) {
final IErlTypespec typespec = (IErlTypespec) child;
if (typespec.getName().equals(typeName)) {
return typespec;
}
}
}
} catch (final ErlModelException e) { // ignore
}
return null;
}
@Override
public IErlPreprocessorDef findPreprocessorDef(final String definedName,
final ErlElementKind kind) {
synchronized (getModelLock()) {
for (final IErlElement m : internalGetChildren()) {
if (m instanceof IErlPreprocessorDef) {
final IErlPreprocessorDef pd = (IErlPreprocessorDef) m;
if (pd.getKind() == kind && pd.getDefinedName().equals(definedName)) {
return pd;
}
}
}
}
return null;
}
@Override
public Collection<ErlangIncludeFile> getIncludeFiles() throws ErlModelException {
if (!isStructureKnown()) {
open(null);
}
final List<ErlangIncludeFile> r = Lists.newArrayList();
synchronized (getModelLock()) {
for (final IErlElement m : internalGetChildren()) {
if (m instanceof IErlAttribute) {
final IErlAttribute a = (IErlAttribute) m;
final OtpErlangObject v = a.getValue();
if (v instanceof OtpErlangString) {
final String s = ((OtpErlangString) v).stringValue();
if ("include".equals(a.getName())) {
r.add(new ErlangIncludeFile(false, s));
} else if ("include_lib".equals(a.getName())) {
r.add(new ErlangIncludeFile(true, s));
}
}
}
}
}
return r;
}
@Override
public Collection<IErlImport> getImports() {
final List<IErlImport> result = new ArrayList<>();
synchronized (getModelLock()) {
for (final IErlElement e : internalGetChildren()) {
if (e instanceof IErlImport) {
final IErlImport ei = (IErlImport) e;
result.add(ei);
}
}
}
return result;
}
@Override
public synchronized void reconcileText(final int offset, final int removeLength,
final String newText, final IProgressMonitor mon) {
if (scanner != null) {
scanner.replaceText(offset, removeLength, newText);
}
if (mon != null) {
mon.worked(1);
}
setStructureKnown(false);
}
@Override
public synchronized void postReconcile(final IProgressMonitor mon) {
try {
open(mon);
} catch (final ErlModelException e) {
ErlLogger.warn(e);
}
if (mon != null) {
mon.worked(1);
}
}
@Override
public synchronized void initialReconcile() {
// currently unused
// Note that the ErlReconciler doesn't send the first full-text
// reconcile that the built-in reconciler does
}
@Override
public synchronized void finalReconcile() {
// currently unused
}
@Override
public String getModuleName() {
return SystemConfiguration.withoutExtension(getName());
}
@Override
public ErlElementKind getKind() {
return ErlElementKind.MODULE;
}
@Override
public void dispose() {
if (scanner != null) {
scanner.dispose();
scanner = null;
}
ErlangEngine.getInstance().getModel().removeModule(this);
}
@Override
public Set<ISourceUnit> getDirectDependentModules() throws ErlModelException {
final Set<ISourceUnit> result = new HashSet<>();
final IErlProject project = modelUtilService.getProject(this);
for (final IErlModule module : project.getModules()) {
final boolean wasOpen = module.isOpen();
if (!wasOpen) {
module.open(null);
}
final Collection<ErlangIncludeFile> incs = module.getIncludeFiles();
for (final ErlangIncludeFile inc : incs) {
if (inc.getFilename().equals(getName())) {
result.add(module);
break;
}
}
if (!wasOpen) {
module.close();
}
}
return result;
}
@Override
public Set<ISourceUnit> getAllDependentModules() throws CoreException {
final Set<ISourceUnit> result = new HashSet<>();
final IErlProject project = modelUtilService.getProject(this);
for (final IErlModule module : project.getModules()) {
final Collection<IErlModule> allIncludedFiles = ErlangEngine.getInstance()
.getModelSearcherService().findAllIncludedFiles(module);
if (allIncludedFiles.contains(this)) {
result.add(module);
}
}
return result;
}
@Override
public synchronized void resetAndCacheScannerAndParser(final String newText)
throws ErlModelException {
initialText = newText;
parsed = false;
setStructureKnown(false);
scanner.initialScan(newText, "", logging);
final boolean built = buildStructure(null);
setStructureKnown(built);
}
@Override
public void setResource(final IFile file) {
this.file = file;
}
@Override
public ScannerService getScanner() {
if (scanner == null) {
scanner = getNewScanner();
} else {
scanner.addref();
}
return scanner;
}
@Override
public void createScanner() {
if (scanner != null) {
scanner.dispose();
}
scanner = getNewScanner();
}
private ScannerService getNewScanner() {
final String filePath = getFilePath();
final String text = getInitialText();
scanner = ErlangEngine.getInstance().getScannerProviderService().get(scannerName);
scanner.initialScan(text, filePath, logging);
return scanner;
}
@Override
public Collection<IErlPreprocessorDef> getPreprocessorDefs(
final ErlElementKind kind) {
final List<IErlPreprocessorDef> result = Lists.newArrayList();
synchronized (getModelLock()) {
for (final IErlElement e : internalGetChildren()) {
if (e instanceof IErlPreprocessorDef) {
final IErlPreprocessorDef pd = (IErlPreprocessorDef) e;
if (pd.getKind() == kind || kind == ErlElementKind.PROBLEM) {
result.add(pd);
}
}
}
}
return result;
}
@Override
public boolean isOnSourcePath() {
final IParent parent = getParent();
if (parent instanceof IErlFolder) {
final IErlFolder folder = (IErlFolder) parent;
return folder.isOnSourcePath();
}
if (checkPath(
modelUtilService.getProject(this).getProperties().getSourceDirs())) {
return true;
}
return false;
}
@Override
public boolean isOnIncludePath() {
final IParent parent = getParent();
if (parent instanceof IErlFolder) {
final IErlFolder folder = (IErlFolder) parent;
return folder.isOnIncludePath();
}
if (checkPath(
modelUtilService.getProject(this).getProperties().getIncludeDirs())) {
return true;
}
return false;
}
private boolean checkPath(final Collection<IPath> dirs) {
final String thePath = getFilePath();
if (thePath != null) {
final IPath p = new Path(thePath).removeLastSegments(1);
for (final IPath dir : dirs) {
if (dir.equals(p)) {
return true;
}
}
}
return false;
}
@Override
public boolean exportsAllFunctions() {
try {
for (final IErlElement e : getChildrenOfKind(ErlElementKind.ATTRIBUTE)) {
if (e instanceof IErlAttribute) {
final IErlAttribute attr = (IErlAttribute) e;
if (attr.getName().equals("compile")) {
final OtpErlangObject value = attr.getValue();
if (value != null && value.equals(EXPORT_ALL)) {
return true;
}
}
}
}
} catch (final ErlModelException e) { // ignore
}
return false;
}
public String createScannerName() {
final IResource resource = getCorrespondingResource();
if (resource != null) {
return resource.getFullPath().toPortableString().substring(1);
}
int hash;
if (initialText != null && !initialText.isEmpty()) {
// we need to use the initialText if available, to avoid that
// different anonymous modules get the same scanner name (which
// causes problems in erlang compare)
hash = initialText.hashCode();
} else {
hash = hashCode();
}
String name = getName();
if (name == null) {
name = "";
}
return String.format("%s_%08x", name, hash);
}
@Override
public String getScannerName() {
return scannerName;
}
}