/****************************************************************************** * Copyright (c) 2016 Oracle and Liferay * 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: * Konstantin Komissarchik - initial implementation and ongoing maintenance * Gregory Amereson - [363551] JavaTypeConstraintService ******************************************************************************/ package org.eclipse.sapphire.java.jdt.ui.internal; import static java.lang.Math.min; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; 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.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jdt.ui.actions.AddUnimplementedConstructorsAction; import org.eclipse.jdt.ui.actions.FormatAllAction; import org.eclipse.jdt.ui.actions.OrganizeImportsAction; import org.eclipse.jdt.ui.actions.OverrideMethodsAction; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.sapphire.DisposeEvent; import org.eclipse.sapphire.Event; import org.eclipse.sapphire.FilteredListener; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.LocalizableText; import org.eclipse.sapphire.LoggingService; import org.eclipse.sapphire.Property; import org.eclipse.sapphire.PropertyEvent; import org.eclipse.sapphire.ReferenceValue; import org.eclipse.sapphire.Sapphire; import org.eclipse.sapphire.Text; import org.eclipse.sapphire.Value; import org.eclipse.sapphire.java.JavaType; import org.eclipse.sapphire.java.JavaTypeConstraintBehavior; import org.eclipse.sapphire.java.JavaTypeConstraintService; import org.eclipse.sapphire.java.JavaTypeKind; import org.eclipse.sapphire.java.JavaTypeName; import org.eclipse.sapphire.modeling.annotations.Reference; import org.eclipse.sapphire.ui.Presentation; import org.eclipse.sapphire.ui.SapphireAction; import org.eclipse.sapphire.ui.def.ActionHandlerDef; import org.eclipse.sapphire.ui.forms.PropertyEditorActionHandler; import org.eclipse.sapphire.ui.forms.PropertyEditorCondition; import org.eclipse.sapphire.ui.forms.PropertyEditorPart; import org.eclipse.sapphire.ui.forms.swt.FormComponentPresentation; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPartSite; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> * @author <a href="mailto:gregory.amerson@liferay.com">Gregory Amerson</a> */ public abstract class JavaTypeCreateActionHandler extends PropertyEditorActionHandler { @Text( "Java Convention Violation" ) private static LocalizableText discourageDialogTitle; @Text( "The use of the default package is discouraged. Do you want to proceed?" ) private static LocalizableText discourageDefaultPackage; @Text( "Type name is discourage. By convention, Java type names should start with an upper case letter. Do you want to proceed?" ) private static LocalizableText discourageLowerCase; static { LocalizableText.init( JavaTypeCreateActionHandler.class ); } private final JavaTypeKind kind; public JavaTypeCreateActionHandler( final JavaTypeKind kind ) { this.kind = kind; } @Override public void init( final SapphireAction action, final ActionHandlerDef def ) { super.init( action, def ); final Property property = property(); final Listener listener = new FilteredListener<PropertyEvent>() { @Override protected void handleTypedEvent( final PropertyEvent event ) { refreshEnablementState(); } }; property.attach( listener ); attach ( new Listener() { @Override public void handle( final Event event ) { if( event instanceof DisposeEvent ) { property.detach( listener ); } } } ); } @Override protected Object run( final Presentation context ) { final Shell shell = ( (FormComponentPresentation) context ).shell(); final Value<?> javaTypeNameValue = (Value<?>) property(); if( javaTypeNameValue.malformed() ) { return null; } final JavaTypeName javaTypeName = (JavaTypeName) javaTypeNameValue.content(); if( javaTypeName.pkg() == null ) { if( ! MessageDialog.openConfirm( shell, discourageDialogTitle.text(), discourageDefaultPackage.text() ) ) { return null; } } if( ! Character.isUpperCase( javaTypeName.simple().charAt( 0 ) ) ) { if( ! MessageDialog.openConfirm( shell, discourageDialogTitle.text(), discourageLowerCase.text() ) ) { return null; } } final JavaTypeConstraintService javaTypeConstraintService = javaTypeNameValue.service( JavaTypeConstraintService.class ); final IJavaProject jproj = javaTypeNameValue.element().adapt( IJavaProject.class ); JavaTypeName expectedBaseClassTemp = null; final List<JavaTypeName> expectedInterfaces = new ArrayList<JavaTypeName>(); if( javaTypeConstraintService != null ) { final JavaTypeConstraintBehavior behavior = javaTypeConstraintService.behavior(); final Collection<String> types = javaTypeConstraintService.types(); final Iterator<String> iterator = types.iterator(); for( int i = 0, n = ( behavior == JavaTypeConstraintBehavior.ALL ? types.size() : min( 1, types.size() ) ); i < n; i++ ) { final String typeName = iterator.next(); try { final IType type = jproj.findType( typeName.replace( '$', '.' ) ); if( type != null && type.exists() ) { if( type.isClass() ) { expectedBaseClassTemp = new JavaTypeName( typeName ); } else if( type.isInterface() ) { expectedInterfaces.add( new JavaTypeName( typeName ) ); } } } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } } final JavaTypeName expectedBaseClass = expectedBaseClassTemp; final IRunnableWithProgress op = new IRunnableWithProgress() { public void run( final IProgressMonitor monitor ) throws InvocationTargetException, InterruptedException { monitor.beginTask( "", 7 ); final JavaTypeKind kind = JavaTypeCreateActionHandler.this.kind; final StringBuilder buf = new StringBuilder(); if( javaTypeName.pkg() != null ) { buf.append( "package " ); buf.append( javaTypeName.pkg() ); buf.append( ";\n\n" ); } final List<JavaTypeName> imports = new ArrayList<JavaTypeName>(); imports.add( javaTypeName ); String base = null; if ( kind == JavaTypeKind.CLASS && expectedBaseClass != null ) { base = deriveSafeLocalName( expectedBaseClass, imports ); } final List<String> interfaces = new ArrayList<String>(); if( kind == JavaTypeKind.CLASS || kind == JavaTypeKind.INTERFACE ) { for( JavaTypeName t : expectedInterfaces ) { interfaces.add( deriveSafeLocalName( t, imports ) ); } } imports.remove( javaTypeName ); if( ! imports.isEmpty() ) { for( JavaTypeName t : imports ) { buf.append( "import " ); buf.append( t.qualified().replace( '$', '.' ) ); buf.append( ";\n" ); } buf.append( '\n' ); } buf.append( "public " ); switch( kind ) { case ANNOTATION: buf.append( "@interface" ); break; case ENUM: buf.append( "enum" ); break; case INTERFACE: buf.append( "interface" ); break; default: buf.append( "class" ); } buf.append( ' ' ); buf.append( javaTypeName.simple() ); if( base != null ) { buf.append( " extends " ); buf.append( base ); } if( ! interfaces.isEmpty() ) { buf.append( kind == JavaTypeKind.INTERFACE ? " extends " : " implements " ); boolean first = true; for( String implementsType : interfaces ) { if( first ) { first = false; } else { buf.append( ", " ); } buf.append( implementsType ); } } buf.append( "\n{\n}\n" ); monitor.worked( 1 ); final IProject proj = javaTypeNameValue.element().adapt( IProject.class ); final IJavaProject jproj = JavaCore.create( proj ); try { final IPackageFragmentRoot src = src( jproj ); final IPackageFragment pkg = src.createPackageFragment( ( javaTypeName.pkg() == null ? "" : javaTypeName.pkg() ), true, null ); final ICompilationUnit cu = pkg.createCompilationUnit( javaTypeName.simple() + ".java", buf.toString(), true, null ); cu.save( null, true ); monitor.worked( 1 ); final IEditorPart editor = JavaUI.openInEditor( cu ); final IWorkbenchPartSite site = editor.getSite(); monitor.worked( 1 ); if( kind == JavaTypeKind.CLASS ) { final IType type = cu.getType( javaTypeName.simple() ); final ASTParser parser = createAstParser(); parser.setResolveBindings( true ); parser.setSource( cu ); final CompilationUnit ast = (CompilationUnit) parser.createAST( null ); ASTNode node = NodeFinder.perform( ast, type.getNameRange() ); while( ! ( node instanceof AbstractTypeDeclaration ) ) { node = node.getParent(); } final ITypeBinding typeBinding = ( (AbstractTypeDeclaration) node ).resolveBinding(); final IWorkspaceRunnable addUnimplementedConstructorsOp = AddUnimplementedConstructorsAction.createRunnable( ast, typeBinding, null, -1, false, Flags.AccPublic, false ); addUnimplementedConstructorsOp.run( null ); final IWorkspaceRunnable overrideMethodsOp = OverrideMethodsAction.createRunnable( ast, typeBinding, null, -1, false ); overrideMethodsOp.run( null ); } monitor.worked( 1 ); final OrganizeImportsAction organizeImportsAction = new OrganizeImportsAction( site ); organizeImportsAction.run( cu ); monitor.worked( 1 ); final FormatAllAction formatAllAction = new FormatAllAction( site ); formatAllAction.runOnMultiple( new ICompilationUnit[] { cu } ); monitor.worked( 1 ); editor.doSave( null ); monitor.worked( 1 ); } catch( CoreException e ) { Sapphire.service( LoggingService.class ).log( e ); } monitor.done(); } }; try { ( new ProgressMonitorDialog( shell ) ).run( false, false, op ); } catch( InvocationTargetException e ) { Sapphire.service( LoggingService.class ).log( e ); } catch( InterruptedException e ) { // Should not happen. Sapphire.service( LoggingService.class ).log( e ); } return null; } @Override protected boolean computeEnablementState() { boolean enabled = super.computeEnablementState(); if( enabled ) { final ReferenceValue<?,?> ref = (ReferenceValue<?,?>) property(); enabled = false; if( ! ref.malformed() ) { final String typeName = ref.text(); if( typeName != null && typeName.indexOf( '$' ) == -1 && ref.target() == null ) { enabled = true; } } } return enabled; } private final IPackageFragmentRoot src( final IJavaProject project ) throws JavaModelException { for( IPackageFragmentRoot root : project.getPackageFragmentRoots() ) { if( root.getKind() == IPackageFragmentRoot.K_SOURCE ) { return root; } } return null; } private static String deriveSafeLocalName( final JavaTypeName type, final Collection<JavaTypeName> imports ) { boolean collision = false; for( JavaTypeName n : imports ) { if( n.simple().equals( type.simple() ) ) { collision = true; break; } } if( collision ) { return type.qualified().replace( '$', '.' ); } else { imports.add( type ); return type.simple(); } } private static ASTParser createAstParser() { final int level; if( isJavaLanguageSpecSupported( 8 ) ) { // Kepler SR2 with Java 8 Patch; Luna or newer level = 8; } else if( isJavaLanguageSpecSupported( 4 ) ) { // Indigo SR1 or newer level = 4; } else { level = 3; } return ASTParser.newParser( level ); } private static boolean isJavaLanguageSpecSupported( final int version ) { try { AST.class.getField( "JLS" + String.valueOf( version ) ); return true; } catch( final NoSuchFieldException e ) { return false; } } protected static abstract class Condition extends PropertyEditorCondition { @Override protected final boolean evaluate( final PropertyEditorPart part ) { final Property property = part.property(); if( property instanceof Value && property.definition().isOfType( JavaTypeName.class ) ) { final Reference referenceAnnotation = property.definition().getAnnotation( Reference.class ); return ( referenceAnnotation != null && referenceAnnotation.target() == JavaType.class && evaluate( property.service( JavaTypeConstraintService.class ) ) && property.element().adapt( IJavaProject.class ) != null ); } return false; } protected abstract boolean evaluate( JavaTypeConstraintService javaTypeConstraintService ); } }