| /******************************************************************************* |
| * Copyright (c) 2013 BSI Business Systems Integration AG. |
| * 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: |
| * BSI Business Systems Integration AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.scout.sdk.classid; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jdt.core.IAnnotation; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.ISourceRange; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.jdt.core.search.IJavaSearchConstants; |
| import org.eclipse.jdt.core.search.SearchEngine; |
| import org.eclipse.jdt.core.search.SearchMatch; |
| import org.eclipse.jdt.core.search.SearchParticipant; |
| import org.eclipse.jdt.core.search.SearchPattern; |
| import org.eclipse.jdt.core.search.SearchRequestor; |
| import org.eclipse.jdt.core.search.TypeReferenceMatch; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.scout.commons.StringUtility; |
| import org.eclipse.scout.commons.job.JobEx; |
| import org.eclipse.scout.sdk.ScoutSdkCore; |
| import org.eclipse.scout.sdk.Texts; |
| import org.eclipse.scout.sdk.extensions.runtime.classes.IRuntimeClasses; |
| import org.eclipse.scout.sdk.internal.ScoutSdk; |
| import org.eclipse.scout.sdk.util.jdt.IJavaResourceChangedListener; |
| import org.eclipse.scout.sdk.util.jdt.JdtEvent; |
| import org.eclipse.scout.sdk.util.jdt.JdtUtility; |
| import org.eclipse.scout.sdk.util.log.ScoutStatus; |
| import org.eclipse.scout.sdk.util.type.TypeUtility; |
| |
| /** |
| * <h3>{@link ClassIdValidationJob}</h3> |
| * |
| * @author Matthias Villiger |
| * @since 4.0.0 20.05.2014 |
| */ |
| public final class ClassIdValidationJob extends JobEx { |
| |
| public static final String CLASS_ID_VALIDATION_JOB_FAMILY = "CLASS_ID_VALIDATION_JOB_FAMILY"; |
| public static final String CLASS_ID_DUPLICATE_MARKER_ID = "org.eclipse.scout.sdk.classid.duplicate"; |
| public static final String CLASS_ID_ATTR_ANNOTATION = "SCOUT_CLASS_ID_ATTR_ANNOTATION"; |
| |
| private static IJavaResourceChangedListener listener; |
| private final IType m_classIdType; |
| |
| private ClassIdValidationJob(IType classIdType) { |
| super(ClassIdValidationJob.class.getName()); |
| setSystem(true); |
| setUser(false); |
| setRule(new P_SchedulingRule()); |
| m_classIdType = classIdType; |
| } |
| |
| private Set<IAnnotation> getAllClassIdAnnotationsInWorkspace(IProgressMonitor monitor) { |
| final HashSet<IAnnotation> result = new HashSet<IAnnotation>(); |
| try { |
| SearchEngine e = new SearchEngine(); |
| e.search(SearchPattern.createPattern(m_classIdType, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, SearchPattern.R_EXACT_MATCH), |
| new SearchParticipant[]{SearchEngine.getDefaultSearchParticipant()}, |
| SearchEngine.createWorkspaceScope(), new SearchRequestor() { |
| @Override |
| public void acceptSearchMatch(SearchMatch match) throws CoreException { |
| IJavaElement element = ((TypeReferenceMatch) match).getLocalElement(); |
| if (element == null) { |
| // e.g. when the annotation is fully qualified. try reading from owner |
| Object owner = match.getElement(); |
| if (owner instanceof IType) { |
| IType ownerType = (IType) owner; |
| if (!ownerType.isBinary() && TypeUtility.exists(ownerType)) { |
| element = JdtUtility.getAnnotation(ownerType, IRuntimeClasses.ClassId); |
| } |
| } |
| } |
| |
| if (element instanceof IAnnotation && !element.isReadOnly() && TypeUtility.exists(element)) { |
| result.add((IAnnotation) element); |
| } |
| } |
| }, monitor); |
| } |
| catch (OperationCanceledException oce) { |
| //nop |
| } |
| catch (CoreException ex) { |
| ScoutSdk.logError("unable to find @ClassId annotation references in workspace.", ex); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return CLASS_ID_VALIDATION_JOB_FAMILY.equals(family); |
| } |
| |
| public synchronized static void install() { |
| if (listener == null) { |
| listener = new P_ResourceChangeListener(); |
| ScoutSdkCore.getJavaResourceChangedEmitter().addJavaResourceChangedListener(listener); |
| } |
| } |
| |
| public synchronized static void uninstall() { |
| if (listener != null) { |
| ScoutSdkCore.getJavaResourceChangedEmitter().removeJavaResourceChangedListener(listener); |
| } |
| Job.getJobManager().cancel(CLASS_ID_VALIDATION_JOB_FAMILY); |
| } |
| |
| private Map<String /*classid*/, List<IAnnotation>> getClassIdOccurrences(IProgressMonitor monitor) throws CoreException { |
| Map<String, List<IAnnotation>> ids = new HashMap<String, List<IAnnotation>>(); |
| Set<IAnnotation> allClassIdAnnotationsInWorkspace = getAllClassIdAnnotationsInWorkspace(monitor); |
| if (monitor.isCanceled()) { |
| return null; |
| } |
| |
| for (IAnnotation r : allClassIdAnnotationsInWorkspace) { |
| if (monitor.isCanceled()) { |
| return null; |
| } |
| |
| if (TypeUtility.exists(r)) { |
| String id = JdtUtility.getAnnotationValueString(r, "value"); |
| if (!StringUtility.isNullOrEmpty(id)) { |
| List<IAnnotation> files = ids.get(id); |
| if (files == null) { |
| files = new LinkedList<IAnnotation>(); |
| ids.put(id, files); |
| } |
| files.add(r); |
| } |
| } |
| } |
| return ids; |
| } |
| |
| private Set<IAnnotation> getVisibleClassIds(IAnnotation current, List<IAnnotation> matchesById) { |
| Set<IAnnotation> visibleMatches = new HashSet<IAnnotation>(matchesById.size()); |
| for (IAnnotation m : matchesById) { |
| if (m != current && TypeUtility.isOnClasspath(m, current.getJavaProject())) { |
| visibleMatches.add(m); |
| } |
| } |
| return visibleMatches; |
| } |
| |
| private void createDuplicateMarkers(Map<String, List<IAnnotation>> annotations) throws CoreException { |
| for (Entry<String, List<IAnnotation>> matches : annotations.entrySet()) { |
| List<IAnnotation> matchesById = matches.getValue(); |
| if (matchesById.size() > 1) { |
| for (IAnnotation duplicate : matchesById) { |
| IType parent = (IType) duplicate.getAncestor(IJavaElement.TYPE); |
| if (TypeUtility.exists(parent)) { |
| // duplicate found: check if they can see each others |
| Set<IAnnotation> visibleDuplicates = getVisibleClassIds(duplicate, matchesById); |
| if (visibleDuplicates.size() > 0) { |
| ISourceRange sourceRange = duplicate.getSourceRange(); |
| if (sourceRange != null && sourceRange.getOffset() >= 0) { |
| IMarker marker = duplicate.getResource().createMarker(CLASS_ID_DUPLICATE_MARKER_ID); |
| marker.setAttribute(IMarker.MESSAGE, Texts.get("DuplicateClassIdValue", matches.getKey(), parent.getFullyQualifiedName())); |
| marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); |
| marker.setAttribute(IMarker.CHAR_START, sourceRange.getOffset()); |
| marker.setAttribute(IMarker.CHAR_END, sourceRange.getOffset() + sourceRange.getLength()); |
| marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); |
| try { |
| Document doc = new Document(parent.getCompilationUnit().getSource()); |
| marker.setAttribute(IMarker.LINE_NUMBER, doc.getLineOfOffset(sourceRange.getOffset()) + 1); |
| } |
| catch (BadLocationException e) { |
| //nop |
| } |
| marker.setAttribute(CLASS_ID_ATTR_ANNOTATION, duplicate); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void deleteDuplicateMarkers() { |
| try { |
| ResourcesPlugin.getWorkspace().getRoot().deleteMarkers(CLASS_ID_DUPLICATE_MARKER_ID, true, IResource.DEPTH_INFINITE); |
| } |
| catch (CoreException e) { |
| ScoutSdk.logError("unable to remove old class id duplicate markers", e); |
| } |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| Map<String, List<IAnnotation>> classIdOccurrences = getClassIdOccurrences(monitor); |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| |
| deleteDuplicateMarkers(); |
| createDuplicateMarkers(classIdOccurrences); |
| return Status.OK_STATUS; |
| } |
| catch (Exception e) { |
| return new ScoutStatus("Error while updating class id duplicate markers.", e); |
| } |
| } |
| |
| private static final class P_ResourceChangeListener implements IJavaResourceChangedListener { |
| @Override |
| public void handleEvent(JdtEvent event) { |
| if (event.getElementType() == IJavaElement.ANNOTATION) { |
| IAnnotation annotation = (IAnnotation) event.getElement(); |
| if (annotation != null && annotation.getElementName().endsWith(Signature.getSimpleName(IRuntimeClasses.ClassId))) { |
| if (TypeUtility.exists(event.getElement())) { |
| executeAsync(1000); |
| } |
| } |
| } |
| } |
| } |
| |
| public static synchronized void executeAsync(final long startDelay) { |
| Job j = new Job("schedule classid validation") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| // get the class id type outside of the validation job |
| // because with the job rule a search cannot be performed -> IllegalArgumentException: Attempted to beginRule |
| ScoutSdkCore.getScoutWorkspace().getBundleGraph().waitFor(); |
| IType classId = TypeUtility.getType(IRuntimeClasses.ClassId); |
| if (TypeUtility.exists(classId)) { |
| Job.getJobManager().cancel(CLASS_ID_VALIDATION_JOB_FAMILY); |
| new ClassIdValidationJob(classId).schedule(startDelay); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| j.setSystem(true); |
| j.setUser(false); |
| j.schedule(); |
| } |
| |
| private static final class P_SchedulingRule implements ISchedulingRule { |
| |
| @Override |
| public boolean contains(ISchedulingRule rule) { |
| return rule instanceof P_SchedulingRule; |
| } |
| |
| @Override |
| public boolean isConflicting(ISchedulingRule rule) { |
| return rule instanceof P_SchedulingRule; |
| } |
| } |
| } |