/******************************************************************************* * Copyright (c) 2012 OpenLegacy Inc. * 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: * OpenLegacy Inc. - initial API and implementation *******************************************************************************/ package org.openlegacy.ide.eclipse.preview; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.IFileBuffer; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.ITextOperationTarget; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextViewer; import org.eclipse.swt.custom.CaretEvent; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.ITextEditor; import org.openlegacy.ide.eclipse.components.SnapshotComposite; import org.openlegacy.terminal.TerminalField; import org.openlegacy.terminal.TerminalPosition; import org.openlegacy.terminal.TerminalSnapshot; import org.openlegacy.terminal.render.DefaultTerminalSnapshotImageRenderer; import org.openlegacy.terminal.support.DefaultTerminalSnapshotsLoader; import org.openlegacy.terminal.support.SimpleTerminalPosition; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class ScreenPreview extends ViewPart { /** * The ID of the view as specified by the extension. */ public static final String ID = "com.mif.plugin.adapter.views.ScreenPreview"; private static final String SCREEN_ENTITY_ANNOTATION = "org.openlegacy.annotations.screen.ScreenEntity"; private static final String SCREEN_ENTITY_ANNOTATION_SHORT = "ScreenEntity"; private static final String IDENTIFIER_ANNOTATION = "org.openlegacy.annotations.screen.Identifier"; private static final String SCREEN_FIELD_ANNOTATION = "org.openlegacy.annotations.screen.ScreenField"; private final String XML_EXTENSION = ".xml"; private SnapshotComposite snapshotComposite; private EditorListener editorListener; private boolean isVisible; private IEditorPart lastActiveEditor; private Map<String, JavaClassProperties> cacheJavaClassPropertiesContainer = new HashMap<String, JavaClassProperties>(); private Map<String, StyledText> cacheStyledTextContainer = new HashMap<String, StyledText>(); private Map<String, CompilationUnit> cacheCompilationUnitContainer = new HashMap<String, CompilationUnit>(); private TerminalSnapshot terminalSnapshot; /** * The constructor. */ public ScreenPreview() {} // **************** OVERRIDEN **************** /** * This is a callback that will allow us to create the viewer and initialize it. */ @Override public void createPartControl(Composite parent) { snapshotComposite = new SnapshotComposite(parent); snapshotComposite.setIsScalable(true); snapshotComposite.initDoubleClickEnlargeListener(); } /** * Passing the focus request to the viewer's control. */ @Override public void setFocus() { snapshotComposite.setFocus(); } @Override public void init(IViewSite site) throws PartInitException { super.init(site); if (editorListener == null) { editorListener = new EditorListener(this); // set Part listener getSite().getWorkbenchWindow().getPartService().addPartListener(editorListener); // set FileBuffer listener FileBuffers.getTextFileBufferManager().addFileBufferListener(editorListener); } } @Override public void dispose() { super.dispose(); if (editorListener != null) { // remove Part listener getSite().getWorkbenchWindow().getPartService().removePartListener(editorListener); // remove FileBuffer listener FileBuffers.getTextFileBufferManager().removeFileBufferListener(editorListener); // remove Caret & Modify listeners for (StyledText styledText : cacheStyledTextContainer.values()) { if (!styledText.isDisposed()) { styledText.removeCaretListener(editorListener); styledText.removeModifyListener(editorListener); } } editorListener.dispose(); editorListener = null; } } // **************** PUBLIC **************** public void showMessage(String message) { MessageDialog.openInformation(snapshotComposite.getShell(), "Screen Preview", message); } public void handlePartActivated(IWorkbenchPart part) { doPartActivated(); } public void handlePartClosed(IWorkbenchPart part) { // hide image if editor with annotation closed if (part == lastActiveEditor) { snapshotComposite.setVisible(false); } } public void handlePartHidden(IWorkbenchPart part) { if (this == part) { isVisible = false; } } public void handleBufferIsDirty(IFileBuffer buffer) { // parse annotation(buffer) to update in cache String key = buffer.getLocation().toOSString(); if (cacheJavaClassPropertiesContainer.containsKey(key)) { JavaClassProperties properties = getJavaClassProperties(key, buffer); cacheJavaClassPropertiesContainer.put(key, properties); } doPartActivated(); } public void handlePartVisible(IWorkbenchPart part) { if (this == part) { if (isVisible) { return; } isVisible = true; } } public void handleCaretMoved(CaretEvent caretEvent) { handleCaretMoved(caretEvent.caretOffset); } public void handleModifyText(ModifyEvent event) { IJavaElement javaInput = getJavaInput(); if (javaInput == null) { return; } String key = javaInput.getPath().toOSString(); if (!cacheCompilationUnitContainer.containsKey(key)) { return; } CompilationUnit cu = createParser((ICompilationUnit)javaInput); cacheCompilationUnitContainer.put(key, cu); StyledText styledText = cacheStyledTextContainer.get(key); if ((styledText != null) && !styledText.isDisposed()) { this.handleCaretMoved(styledText.getCaretOffset()); } } // **************** PRIVATE **************** /** * Extract IJavaElement if current document is a java file * * @return IJavaElement or null */ private static IJavaElement getJavaInput() { IEditorPart activeEditor = getActiveEditor(); if (activeEditor != null) { if (!(activeEditor instanceof ITextEditor)) { return null; } final IJavaElement javaInput = getJavaInput(activeEditor); if (javaInput != null && javaInput.getPath().getFileExtension().equals("java")) { return javaInput; } } return null; } /** * Extract JavaClassProperties if current source annotated with <b>@ScreenEntity</b> * * @return JavaClassProperties instance or null */ private JavaClassProperties getJavaClassProperties(String key, Object source) { final AtomicBoolean isAccepted = new AtomicBoolean(false); IFile xmlFile = null; CompilationUnit cu = cacheCompilationUnitContainer.get(key); if (cu == null) { // create parser if (source instanceof ITextFileBuffer) { cu = createParser(((ITextFileBuffer)source).getDocument().get()); } else { cu = createParser((ICompilationUnit)source); } } cacheCompilationUnitContainer.put(key, cu); if (cu == null) { return null; } final List<String> imports = prepareImports(cu.imports().toArray()); // run through the code and visit Annotation nodes cu.accept(new ASTVisitor() { @Override public boolean visit(NormalAnnotation node) { if (node != null && !isAccepted.get()) { isAccepted.set(inspectAnnotationNode(node, imports)); } return true; } }); if (isAccepted.get()) { xmlFile = getXmlFile(cu.getJavaElement().getPath()); } return new JavaClassProperties(xmlFile, isAccepted.get()); } private static List<String> prepareImports(Object[] imports) { List<String> importList = new ArrayList<String>(); for (Object importItem : imports) { String string = importItem.toString(); importList.add(string.substring(string.indexOf("import") + "import".length(), string.lastIndexOf(";")).trim()); } return importList; } private static IFile getFile(String path) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); return root.getFile(new Path(path)); } private void showPreviewImage(IJavaElement javaInput, IFile xmlFile) throws JavaModelException { if (xmlFile != null && xmlFile.exists()) { // show snapshot composite DefaultTerminalSnapshotsLoader loader = new DefaultTerminalSnapshotsLoader(); terminalSnapshot = loader.load(new File(xmlFile.getLocationURI()).getAbsolutePath()); snapshotComposite.setSnapshot(terminalSnapshot); snapshotComposite.setVisible(true); return; } // hide image snapshotComposite.setVisible(false); } private IFile getXmlFile(IPath path) { String className = path.lastSegment().replace("." + path.getFileExtension(), ""); StringBuilder builder = new StringBuilder(path.removeFileExtension().toOSString()); builder.append("-resources"); builder.append(File.separator); builder.append(className + XML_EXTENSION); return getFile(builder.toString()); } private void doPartActivated() { if (isVisible) { IJavaElement javaInput = getJavaInput(); if (javaInput != null) { final AtomicBoolean isAccepted = new AtomicBoolean(false); String key = javaInput.getPath().toOSString(); if (!cacheJavaClassPropertiesContainer.containsKey(key)) { JavaClassProperties properties = getJavaClassProperties(key, javaInput); cacheJavaClassPropertiesContainer.put(key, properties); isAccepted.set(properties != null); } else { JavaClassProperties properties = cacheJavaClassPropertiesContainer.get(key); // if cached class is annotated then check if there is the latest version of image if (properties.isAnnotated()) { isAccepted.set(true); IFile xmlFile = null; xmlFile = properties.getXmlFile(); IFile newXmlFile = getXmlFile(javaInput.getPath()); if (xmlFile.getModificationStamp() != newXmlFile.getModificationStamp()) { xmlFile = newXmlFile; properties.setXmlFile(newXmlFile); } } } if (isAccepted.get()) { // add caret listener if (!cacheStyledTextContainer.containsKey(key) || cacheStyledTextContainer.get(key).isDisposed()) { AbstractTextEditor editor = (AbstractTextEditor)getActiveEditor(); StyledText styledText = ((StyledText)editor.getAdapter(Control.class)); styledText.addCaretListener(editorListener); styledText.addModifyListener(editorListener); cacheStyledTextContainer.put(key, styledText); } try { // show image if it exists showPreviewImage(javaInput, cacheJavaClassPropertiesContainer.get(key).getXmlFile()); lastActiveEditor = getActiveEditor(); return; } catch (JavaModelException e) { e.printStackTrace(); } } } } // hide image snapshotComposite.setVisible(false); } private static CompilationUnit createParser(ICompilationUnit javaInput) { ASTParser parser = ASTParser.newParser(AST.JLS4); parser.setSource(javaInput); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setResolveBindings(true); return (CompilationUnit)parser.createAST(null); } private static CompilationUnit createParser(String input) { ASTParser parser = ASTParser.newParser(AST.JLS4); parser.setSource(input.toCharArray()); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setResolveBindings(true); return (CompilationUnit)parser.createAST(null); } private static boolean inspectAnnotationNode(NormalAnnotation node, List<String> imports) { Name typeName = node.getTypeName(); ITypeBinding binding = typeName.resolveTypeBinding(); if (binding != null) { String qualifiedName = binding.getQualifiedName(); if (qualifiedName != null) { if (qualifiedName.equals(SCREEN_ENTITY_ANNOTATION)) { return true; } } } else { // in case if binding is not resolved String fullyQualifiedName = typeName.getFullyQualifiedName(); if (fullyQualifiedName.equals(SCREEN_ENTITY_ANNOTATION) || fullyQualifiedName.equals(SCREEN_ENTITY_ANNOTATION_SHORT)) { if (imports.contains(SCREEN_ENTITY_ANNOTATION) || imports.contains(SCREEN_ENTITY_ANNOTATION.replace(SCREEN_ENTITY_ANNOTATION_SHORT, "*"))) { return true; } } } return false; } private static IEditorPart getActiveEditor() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window != null) { IWorkbenchPage page = window.getActivePage(); if (page != null) { return page.getActiveEditor(); } } return null; } private static IJavaElement getJavaInput(IEditorPart part) { IEditorInput editorInput = part.getEditorInput(); if (editorInput != null) { IJavaElement input = (IJavaElement)editorInput.getAdapter(IJavaElement.class); return input; } return null; } private void handleCaretMoved(int widgetCaretOffset) { IJavaElement javaInput = getJavaInput(); if (javaInput == null) { return; } String key = javaInput.getPath().toOSString(); CompilationUnit cu = cacheCompilationUnitContainer.get(key); if (cu == null) { return; } IEditorPart activeEditor = getActiveEditor(); final AtomicInteger offset = new AtomicInteger(0); ITextOperationTarget target = (ITextOperationTarget)activeEditor.getAdapter(ITextOperationTarget.class); if (target instanceof ITextViewer) { TextViewer textViewer = (TextViewer)target; offset.set(textViewer.widgetOffset2ModelOffset(widgetCaretOffset)); } final AtomicBoolean isAnnotationVisited = new AtomicBoolean(false); cu.accept(new ASTVisitor() { @Override public void endVisit(FieldDeclaration node) { super.endVisit(node); if (isAnnotationVisited.get()) { return; } if ((offset.get() < node.getStartPosition()) || (offset.get() > (node.getStartPosition() + node.getLength()))) { return; } List<ASTNode> modifiers = castList(ASTNode.class, node.modifiers()); for (ASTNode astNode : modifiers) { if (astNode instanceof NormalAnnotation) { ITypeBinding binding = ((NormalAnnotation)astNode).resolveTypeBinding(); // binding can be null, for example if user copy field with annotation if (binding == null) { continue; } if (binding.getQualifiedName().equals(SCREEN_FIELD_ANNOTATION)) { // retrieve rectangle from annotation attributes isAnnotationVisited.set(checkVisitedAnnotation((NormalAnnotation)astNode)); } } } } @Override public void endVisit(NormalAnnotation node) { super.endVisit(node); if (isAnnotationVisited.get()) { return; } if ((offset.get() < node.getStartPosition()) || (offset.get() > (node.getStartPosition() + node.getLength()))) { snapshotComposite.setDrawingRectangle(null); return; } ITypeBinding binding = node.resolveTypeBinding(); // binding can be null, for example if user copy field with annotation if (binding == null) { return; } if (binding.getQualifiedName().equals(IDENTIFIER_ANNOTATION) || binding.getQualifiedName().equals(SCREEN_FIELD_ANNOTATION)) { // retrieve rectangle from annotation attributes isAnnotationVisited.set(checkVisitedAnnotation(node)); } } }); } private boolean checkVisitedAnnotation(NormalAnnotation annotation) { int row = 0; int col = 0; int endCol = 0; String val = ""; List<MemberValuePair> values = castList(MemberValuePair.class, annotation.values()); for (MemberValuePair pair : values) { SimpleName key = pair.getName(); String value = pair.getValue().toString(); if (key.getIdentifier().equalsIgnoreCase("row") && isInteger(value)) { row = new Integer(value).intValue(); } if (key.getIdentifier().equalsIgnoreCase("column") && isInteger(value)) { col = new Integer(value).intValue(); } if (key.getIdentifier().equalsIgnoreCase("endColumn") && isInteger(value)) { endCol = new Integer(value).intValue(); } if (key.getIdentifier().equalsIgnoreCase("value")) { val = value; } } snapshotComposite.setDrawingRectangle(getRectangle(row, col, endCol, val)); return true; } private static boolean isInteger(String value) { try { Integer.parseInt(value); return true; } catch (NumberFormatException e) { return false; } } private static <T> List<T> castList(Class<? extends T> clazz, Collection<?> c) { List<T> r = new ArrayList<T>(c.size()); for (Object o : c) { r.add(clazz.cast(o)); } return r; } private Rectangle getRectangle(int row, int column, int endColumn, String value) { DefaultTerminalSnapshotImageRenderer renderer = new DefaultTerminalSnapshotImageRenderer(); int length = 0; if (endColumn == 0) { SimpleTerminalPosition terminalPosition = new SimpleTerminalPosition(row, column); TerminalField field = terminalSnapshot.getField(terminalPosition); if (field != null) { TerminalPosition endPosition = field.getEndPosition(); endColumn = endPosition.getColumn(); } else { // if row or column attribute was changed then calculate endColumn endColumn = column + value.length() - 1; } } length = endColumn - column + 1; int x = renderer.toWidth(column - 1 + renderer.getLeftColumnsOffset()); int y = renderer.toHeight(row - 1) + renderer.getTopPixelsOffset(); int width = renderer.toWidth(length); int height = renderer.toHeight(1); return new Rectangle(x, y, width, height); } }