/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.beans.beaninfo;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.swing.SwingUtilities;
import javax.swing.text.StyledDocument;
import javax.tools.Diagnostic;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.modules.beans.BeanUtils;
import org.netbeans.modules.beans.EventSetPattern;
import org.netbeans.modules.beans.GenerateBeanException;
import org.netbeans.modules.beans.IdxPropertyPattern;
import org.netbeans.modules.beans.PatternAnalyser;
import org.netbeans.modules.beans.PropertyPattern;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.cookies.EditorCookie;
import org.openide.loaders.DataObject;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/** Analyses the ClassElement trying to find source code patterns i.e.
 * properties or event sets;
 *
 * @author Petr Hrebejk, Petr Suchomel
 */

public final class BiAnalyser {

    private static final String TAB = "    "; // NOI18N
    private static final String TABx2 = TAB +TAB;
    private static final String TABx3 = TAB + TABx2;
    
    private static final String NOI18N_COMMENT = " // NOI18N"; // NOI18N

    private static final String ICONNAME_C16 = "iconNameC16"; // NOI18N
    private static final String ICONNAME_C32 = "iconNameC32"; // NOI18N
    private static final String ICONNAME_M16 = "iconNameM16"; // NOI18N
    private static final String ICONNAME_M32 =  "iconNameM32"; // NOI18N

    private static final String DEFAULT_PROPERTY_INDEX = "defaultPropertyIndex"; // NOI18N
    private static final String DEFAULT_EVENT_INDEX = "defaultEventIndex"; // NOI18N

    /** Holds Bean descriptor */
    List<BiFeature.Descriptor> descriptor;
    
    /** Holds all properties */
    List<BiFeature.Property> properties;

    /** Holds all indexed properties */
    List<BiFeature.IdxProperty> idxProperties;

    /** Holds all events sets */
    List<BiFeature.EventSet> eventSets;

    /** Holds all methods */
    List<BiFeature.Method> methods;

    /** Object representing source code of associated BeanInfo */
    BeanInfoSource bis;

    /** Should bean descriptor be obtained from introspection */
    private boolean nullDescriptor = false;

    /** Should properties be obtained from introspection */
    private boolean nullProperties = false;

    /** Should event sets be obtained from introspection */
    private boolean nullEventSets = false;

    /** Should methods be obtained from introspection */
    private boolean nullMethods = false;

    /** Should bean descriptor have lazy init */
    private boolean lazyDescriptor = true;

    /** Should properties have lazy init */
    private boolean lazyProperties = true;

    /** Should event sets have lazy init */
    private boolean lazyEventSets = true;

    /** Should methods have lazy init */
    private boolean lazyMethods = true;
    
    /** Is the version of BeanInfo generated by older beans module? */
    private final boolean olderVersion;
    /** Is the version of BeanInfo generated by new beans module with superclass? */
    private boolean superClassVersion=true;

    /* Holds the class for which the bean info is generated */
    private String classfqn;

    private String iconC16;
    private String iconM16;
    private String iconC32;
    private String iconM32;
    private int defaultPropertyIndex = -1;
    private int defaultEventIndex = -1;
    private boolean useSuperClass = false;
    private boolean isModified = false;
    private boolean isIconModified = false;
    private boolean isUpdateMode;
    private boolean isBeanBroken;
    
    private static void removeMethods(List<BiFeature.Method> methods, ElementHandle<ExecutableElement>... removes) {
        List<ElementHandle<ExecutableElement>> realremoves = new ArrayList<ElementHandle<ExecutableElement>>(removes.length);
        for (ElementHandle<ExecutableElement> remove : removes) {
            if (remove != null) {
                realremoves.add(remove);
            }
        }
        if (realremoves.isEmpty()) {
            return;
        }
        for (int i = methods.size() - 1; i >= 0 ; i--) {
            ElementHandle<ExecutableElement> method = methods.get(i).getElement();
            for (int ri = 0; ri < realremoves.size() ; ri++) {
                ElementHandle<ExecutableElement> remove = realremoves.get(ri);
                if (remove.equals(method)) {
                    methods.remove(i);
                    realremoves.remove(ri);
                    break;
                }
            }
            if (realremoves.isEmpty()) {
                return;
            }
        }
    }
    
    /** Creates Bean Info analyser which contains all patterns from PatternAnalyser
    */
    BiAnalyser ( PatternAnalyser pa, CompilationInfo javac ) throws GenerateBeanException {
        int index;
        
        this.isBeanBroken = false;
        
        for (Diagnostic d : javac.getDiagnostics()) {
            isBeanBroken |= d.getKind() == Diagnostic.Kind.ERROR;
        }

        // Try to find and analyse existing bean info
        bis = new BeanInfoSource( pa.getFileObject() );
        olderVersion = (bis.isNbBeanInfo() && bis.getMethodsSection() == null);
        superClassVersion = (bis.isNbSuperclass() || !bis.exists());
        
        TypeElement classElement = pa.getClassElementHandle().resolve(javac);
        this.classfqn = classElement.getQualifiedName().toString();
        
        // Fill Descriptor list (only in case we have new templates)
        descriptor = new ArrayList<BiFeature.Descriptor>();
        descriptor.add(new BiFeature.Descriptor(classElement, this));

        // Fill methods list (only in case we have new templates)
        methods = new  ArrayList<BiFeature.Method>();
        if (!olderVersion) {
            for (ExecutableElement method : BeanUtils.methodsIn(classElement, javac)) {
                methods.add(new BiFeature.Method(method, pa, javac, this));
            }
        }

        // Fill properties list
        List<PropertyPattern> propertyPatterns = pa.getPropertyPatterns();
        properties = new  ArrayList<BiFeature.Property>(propertyPatterns.size());
        for (PropertyPattern pp : propertyPatterns) {
            properties.add(new BiFeature.Property(pp, javac, this));
            removeMethods(methods, pp.getGetterMethod(), pp.getSetterMethod());
        }

        // Fill indexed properties list
        List<IdxPropertyPattern> idxPropertyPatterns = pa.getIdxPropertyPatterns();
        idxProperties = new  ArrayList<BiFeature.IdxProperty>(idxPropertyPatterns.size());
        for (IdxPropertyPattern ipp : idxPropertyPatterns) {
            TypeMirror type = ipp.getType().resolve(javac);
            TypeMirror idxtype = ipp.getIndexedType().resolve(javac);
            if (type.getKind() != TypeKind.ARRAY || !javac.getTypes().isSameType(((ArrayType) type).getComponentType(), idxtype)) {
                continue;
            }

            idxProperties.add(new BiFeature.IdxProperty(ipp, javac, this));
            removeMethods(methods, ipp.getGetterMethod(), ipp.getSetterMethod(), ipp.getIndexedGetterMethod(), ipp.getIndexedSetterMethod());
        }

        // Fill event sets list
        List<EventSetPattern> eventSetPatterns = pa.getEventSetPatterns();
        eventSets = new  ArrayList<BiFeature.EventSet>(eventSetPatterns.size());
        for (EventSetPattern esp : eventSetPatterns) {
            eventSets.add(new BiFeature.EventSet(esp, javac, this));
            removeMethods(methods, esp.getAddListenerMethod(), esp.getRemoveListenerMethod());
        }

        Collections.sort(methods);

        try {
            isUpdateMode = false;
            analyzeBeanInfoSource();
        } finally {
            isUpdateMode = true;
        }

    }
    
    List<BiFeature.Descriptor> getDescriptor() {
        return descriptor;
    }
    
    List<BiFeature.Property> getProperties() {
        return properties;
    }

    List<BiFeature.IdxProperty> getIdxProperties() {
        return idxProperties;
    }

    List<BiFeature.EventSet> getEventSets() {
        return eventSets;
    }

    List<BiFeature.Method> getMethods() {
        return methods;
    }

    public boolean isOlderVersion() {
        return olderVersion;
    }
    
    public boolean isSuperclassVersion() {
        return superClassVersion;
    }

    public String getIconC16() {
        return iconC16;
    }

    public void setIconC16(String iconC16) {
        this.iconC16 = iconC16;
        setIconModified();
    }

    public String getIconM16() {
        return iconM16;
    }

    public void setIconM16(String iconM16) {
        this.iconM16 = iconM16;
        setIconModified();
    }

    public String getIconC32() {
        return iconC32;
    }

    public void setIconC32(String iconC32) {
        this.iconC32 = iconC32;
        setIconModified();
    }

    public String getIconM32() {
        return iconM32;
    }

    public void setIconM32(String iconM32) {
        this.iconM32 = iconM32;
        setIconModified();
    }

    public int getDefaultPropertyIndex() {
        return defaultPropertyIndex;
    }

    public void setDefaultPropertyIndex(int defaultPropertyIndex) {
        this.defaultPropertyIndex = defaultPropertyIndex;
        setModified();
    }

    public int getDefaultEventIndex() {
        return defaultEventIndex;
    }

    public void setDefaultEventIndex(int defaultEventIndex) {
        this.defaultEventIndex = defaultEventIndex;
        setModified();
    }

    /** Getter for property useSuperClass.
     * @return Value of property useSuperClass.
     */
    public boolean isUseSuperClass() {
        return this.useSuperClass;
    }
    
    /** Setter for property useSuperClass.
     * @param useSuperClass New value of property useSuperClass.
     */
    public void setUseSuperClass(boolean useSuperClass) {
        this.useSuperClass = useSuperClass;
        setModified();
    }
    
    boolean isNullDescriptor() {
        return nullDescriptor;
    }

    boolean isNullProperties() {
        return nullProperties;
    }

    boolean isNullMethods() {
        return nullMethods;
    }

    void setNullDescriptor( boolean nullDescriptor ) {
        this.nullDescriptor = nullDescriptor;
        setModified();
    }

    void setNullProperties( boolean nullProperties ) {
        this.nullProperties = nullProperties;
        setModified();
    }

    void setNullMethods( boolean nullMethods ) {
        this.nullMethods = nullMethods;
        setModified();
    }

    boolean isNullEventSets() {
        return nullEventSets;
    }

    void setNullEventSets( boolean nullEventSets ) {
        this.nullEventSets = nullEventSets;
        setModified();
    }

    public boolean isLazyDescriptor() {
        return lazyDescriptor;
    }

    public boolean isLazyProperties() {
        return lazyProperties;
    }

    public boolean isLazyMethods() {
        return lazyMethods;
    }

    public void setLazyDescriptor( boolean lazyDescriptor ) {
        this.lazyDescriptor = lazyDescriptor;
        setModified();
    }

    public void setLazyProperties( boolean lazyProperties ) {
        this.lazyProperties = lazyProperties;
        setModified();
    }

    public void setLazyMethods( boolean lazyMethods ) {
        this.lazyMethods = lazyMethods;
        setModified();
    }

    public boolean isLazyEventSets() {
        return lazyEventSets;
    }

    public void setLazyEventSets( boolean lazyEventSets ) {
        this.lazyEventSets = lazyEventSets;
        setModified();
    }
    
    void regenerateSource() {
        if ( bis.exists() && !bis.isNbBeanInfo()) {
            throw new IllegalStateException();
        }
        
        DataObject dataObject = bis.getDataObject();
        EditorCookie editor = dataObject.getLookup().lookup(EditorCookie.class);
        final StyledDocument doc = editor.getDocument();
        Runnable task = new Runnable() {

                public void run() {
                    regenerateSourceImpl(doc);
                }
            };
            
        if (SwingUtilities.isEventDispatchThread()) {
            task.run();
        } else {
            try {
                SwingUtilities.invokeAndWait(task);
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (InvocationTargetException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }
    
    void regenerateSourceImpl(StyledDocument doc) {
        NbDocument.runAtomic(doc, new Runnable() {
                public void run()  {
                    regenerateBeanDescriptor();
                    regenerateProperties();
                    regenerateEvents();
                    if (!olderVersion) {
                        regenerateMethods();
                    }
                    regenerateIcons();
                    regenerateDefaultIdx();
                    regenerateSuperclass();
                    isModified = false;
                    isIconModified = false;
                }
        } );
    }
    
    void openSource() {
        String mssg;
        NotifyDescriptor nd;

        if ( bis.exists() ) {

            if ( !bis.isNbBeanInfo() ) {
                if (isBeanBroken) {
                    mssg = NbBundle.getMessage(BiAnalyser.class, "MSG_BrokenBean", this.bis.getSourceDataObject().getPrimaryFile().getNameExt());
                    nd = new NotifyDescriptor.Message(mssg, NotifyDescriptor.ERROR_MESSAGE);
                    DialogDisplayer.getDefault().notify(nd);
                    return;
                }
                mssg = GenerateBeanInfoAction.getString( "MSG_BeanInfoExists" );  // NOI18N
                nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION );
                DialogDisplayer.getDefault().notify( nd );
                if ( !nd.getValue().equals ( NotifyDescriptor.YES_OPTION ) ) {
                    return;
                }

                try {
                    bis.delete();
                }
                catch ( IOException e ) {
                    mssg = GenerateBeanInfoAction.getString( "MSG_BeanInfoCantDelete" );  // NOI18N
                    nd = new NotifyDescriptor.Message ( mssg );
                    DialogDisplayer.getDefault().notify( nd );
                    return;
                }
                // XXX later the icon section may be autogenerated via Java Source API
//                bis.createFromTemplate(iconBlockRequired());
                bis.createFromTemplate(true);
                regenerateSource();
                BIEditorSupport editor = bis.getDataObject().getLookup().lookup(BIEditorSupport.class);
                try {
                    editor.saveDocument();
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
        else {
            if (isBeanBroken) {
                mssg = NbBundle.getMessage(BiAnalyser.class, "MSG_BrokenBean", this.bis.getSourceDataObject().getPrimaryFile().getNameExt());
                nd = new NotifyDescriptor.Message(mssg, NotifyDescriptor.ERROR_MESSAGE);
                DialogDisplayer.getDefault().notify(nd);
                return;
            }
            // notify user about missing beaninfo and ask if generate new one.
            mssg = NbBundle.getMessage(BiAnalyser.class, "MSG_BeanInfoNotExists");
            nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION );
            DialogDisplayer.getDefault().notify( nd );
            if ( !nd.getValue().equals ( NotifyDescriptor.YES_OPTION ) ) {
                return;
            }
            bis.createFromTemplate(true);
            regenerateSource();
            BIEditorSupport editor = bis.getDataObject().getLookup().lookup(BIEditorSupport.class);
            try {
                editor.saveDocument();
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }

            if ( !bis.isNbBeanInfo() ) {
                // XXX notify user about wrong template
                return;
            }

        }
        
        bis.open();

    }

    private void regenerateBeanDescriptor() {
        StringBuilder sb = new StringBuilder( 512 );
                
        if ( nullDescriptor ) {
            sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_NullDescriptor" ) );  // NOI18N
            sb.append( TAB + "private static BeanDescriptor beanDescriptor = null;\n" ); // NOI18N
            sb.append( TAB  + "private static BeanDescriptor getBdescriptor(){\n\n");  // NOI18N
            bis.setDescriptorSection( sb.toString(), "\n" + TABx2+ "return beanDescriptor;\n" + TAB + "}\n\n" ); // NOI18N
            return;
        }
        
        for (BiFeature.Descriptor bif : getDescriptor()) {
            if( bif.isIncluded() ){
                sb.append( TAB + GenerateBeanInfoAction.getString("COMMENT_BeanDescriptor" ));   // NOI18N
                if( !lazyDescriptor ){
                    //this code is used for static init                    
                    sb.append( TAB + "private static BeanDescriptor beanDescriptor = ");    // NOI18N
                    sb.append( bif.getCreationString() );
                    sb.append(';');
                    appendNoi18nText(sb);
                    sb.append( "\n\n" ); // NOI18N

                    sb.append( TAB  + "private static BeanDescriptor getBdescriptor(){\n");  // NOI18N
                    sb.append( TABx2+ "return beanDescriptor;\n" + TAB + "}\n\n" ); // NOI18N

                    sb.append( TAB + "static {\n" ); // NOI18N
                }
                else {
                    sb.append( TAB + "/*lazy BeanDescriptor*/\n");    // NOI18N
                    sb.append( TAB  + "private static BeanDescriptor getBdescriptor(){\n");  // NOI18N
                    sb.append( TABx2+ "BeanDescriptor beanDescriptor = ");  // NOI18N
                    sb.append( bif.getCreationString() );
                    sb.append(';');
                    appendNoi18nText(sb);
                    sb.append( '\n' ); // NOI18N
                }
                
                for (String line : bif.getCustomizationStrings()) {
                    sb.append( TABx2 + "beanDescriptor."); // NOI18N
                    sb.append( line ).append( ";\n" ); // NOI18N
                }
                if( !lazyDescriptor ){
                    bis.setDescriptorSection( sb.toString(), "}\n"); // NOI18N
                }
                else {
                    bis.setDescriptorSection( sb.toString(), TABx2+ "return beanDescriptor;\n" + TAB+ "}\n"); // NOI18N
                }
            }            
        }
    }
    
    /** Regenerates the property section of BeanInfo */
    private void regenerateProperties() {
        StringBuilder sb = new StringBuilder(512);
        int propertyCount = 0;

        if ( nullProperties ) {
            sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_NullProperties" ) );  // NOI18N
            sb.append( TAB + "private static PropertyDescriptor[] properties = null;\n" ); // NOI18N
            sb.append( TAB  + "private static PropertyDescriptor[] getPdescriptor(){\n");  // NOI18N
            bis.setPropertiesSection( sb.toString(), TABx2+ "return properties;\n" + TAB + "}\n\n" ); // NOI18N
            return;
        }

        // Make common list of all properites
        Set<BiFeature.Property> allProperties = new  TreeSet<BiFeature.Property>(getProperties());
        allProperties.addAll(getIdxProperties());

        sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_PropertyIdentifiers" ) );  // NOI18N

        for (BiFeature.Property bif : allProperties) {

            if ( bif.isIncluded() ) {
                sb.append( TAB + "private static final int " ); // NOI18N
                // This prefix MUST be consistent w/ BiFeature.IdxProperty analyser
                sb.append( "PROPERTY_" + bif.getName() ); // NOI18N
                sb.append( " = " + (propertyCount++) + ";" ); // NOI18N
                sb.append( "\n" ); // NOI18N
            }
        }

        sb.append( "\n" + TAB + GenerateBeanInfoAction.getString("COMMENT_PropertyArray" ));  // NOI18N
        if( !lazyProperties ){            
            sb.append( TAB + "private static PropertyDescriptor[] properties = new PropertyDescriptor[" + // NOI18N
                       propertyCount + "];\n\n" ); // NOI18N
            sb.append( TAB  + "private static PropertyDescriptor[] getPdescriptor(){\n");  // NOI18N
            sb.append( TABx2+ "return properties;\n" + TAB + "}\n\n" ); // NOI18N
        }
        else{
            //lazy init
            sb.append( TAB + "/*lazy PropertyDescriptor*/\n");    // NOI18N
            sb.append( TAB  + "private static PropertyDescriptor[] getPdescriptor(){\n");   // NOI18N
            sb.append( TABx2+ "PropertyDescriptor[] properties = new PropertyDescriptor[");  // NOI18N
            sb.append( propertyCount );
            sb.append( "];\n" ); // NOI18N
        }
        if ( propertyCount > 0) {
            if( !lazyProperties ){
                sb.append( TAB + "static {\n" + TABx2 + "try {\n" ); // NOI18N
            }
            else {
                sb.append( TAB + "\n" + TABx2 + "try {\n" ); // NOI18N
            }
        }
        
        for (BiFeature.Property bif : allProperties) {

            if ( bif.isIncluded() ) {
                sb.append( TABx3 + "properties[PROPERTY_" ).append( bif.getName() ).append("] = "); // NOI18N
                sb.append(bif.getCreationString());
                sb.append(';');
                appendNoi18nText(sb);
                sb.append('\n');

                for (String line : bif.getCustomizationStrings()) {
                    sb.append( TABx3 + "properties[PROPERTY_" ).append( bif.getName() ).append("]."); // NOI18N
                    sb.append( line ).append( ";\n" ); // NOI18N
                }
            }
        }

        if ( propertyCount > 0 )
            sb.append( TABx2 + "}\n" +  TABx2 + "catch(IntrospectionException e) {\n" + TABx3 + "e.printStackTrace();\n" + TABx2 + "}" ); // NOI18N

        if( !lazyProperties ){
            bis.setPropertiesSection( sb.toString(), propertyCount > 0 ? "}\n" : "  \n" ); // NOI18N
        }
        else{
            bis.setPropertiesSection( sb.toString(), TABx2+ "return properties;\n" + TAB + "}\n"); // NOI18N
        }
    }

    /** Regenerates the method section of BeanInfo */
    private void regenerateMethods() {
        StringBuilder sb = new StringBuilder( 512 );
        int methodCount = 0;


        if ( nullMethods ) {
            sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_NullMethods" ) );  // NOI18N
            sb.append( TAB + "private static MethodDescriptor[] methods = null;\n" ); // NOI18N
            sb.append( TAB  + "private static MethodDescriptor[] getMdescriptor(){\n");  // NOI18N
            bis.setMethodsSection( sb.toString(), TABx2+ "return methods;\n" + TAB + "}\n\n" ); // NOI18N
            return;
        }

        // Make common list of all methods
        List<BiFeature.Method> allMethods = methods;

        sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_MethodIdentifiers" ) );  // NOI18N

        for (BiFeature.Method bif : allMethods) {
            if ( bif.isIncluded() ) {
                sb.append( TAB + "private static final int " ); // NOI18N
                sb.append( "METHOD_" + bif.getName() + methodCount ); // NOI18N
                sb.append( " = " + (methodCount++) + ";" ); // NOI18N
                sb.append( "\n" ); // NOI18N
            }
        }

        sb.append( "\n" + TAB + GenerateBeanInfoAction.getString("COMMENT_MethodArray" ));  // NOI18N
        if( !lazyMethods ){  
            sb.append( TAB + "private static MethodDescriptor[] methods = new MethodDescriptor[" + // NOI18N
                       methodCount + "];\n\n" ); // NOI18N
            sb.append( TAB  + "private static MethodDescriptor[] getMdescriptor(){\n");  // NOI18N
            sb.append( TABx2+ "return methods;\n" + TAB + "}\n\n" ); // NOI18N
        }
        else{
            //lazy init
            sb.append( TAB + "/*lazy MethodDescriptor*/\n");    // NOI18N
            sb.append( TAB  + "private static MethodDescriptor[] getMdescriptor(){\n");  // NOI18N
            sb.append( TABx2+ "MethodDescriptor[] methods = new MethodDescriptor[");  // NOI18N
            sb.append( methodCount );
            sb.append( "];\n" ); // NOI18N
        }
            

        if ( methodCount > 0) {
            if( !lazyMethods ){           
                sb.append( TAB + "static {\n" + TABx2 + "try {\n" ); // NOI18N
            }
            else {
                sb.append( TAB + "\n" + TABx2 + "try {\n" ); // NOI18N
            }
        }

        Iterator<BiFeature.Method> it = allMethods.iterator();
        
        for ( int i = 0, lCurMethodCount = 0; it.hasNext(); ) {
            BiFeature bif = it.next();

            if ( bif.isIncluded() ) {
                sb.append( TABx3 + "methods[METHOD_" ).append( bif.getName() ).append(lCurMethodCount++ + "] = "); // NOI18N
                sb.append(bif.getCreationString());
                sb.append(';');
                appendNoi18nText(sb);
                sb.append('\n');

                for (String line : bif.getCustomizationStrings()) {
                    sb.append( TABx3 + "methods[METHOD_" ).append( bif.getName() ).append(i + "]."); // NOI18N
                    sb.append( line ).append( ";\n" ); // NOI18N
                }
                i++;
            }
        }

        if ( methodCount > 0 )
            sb.append( TABx2 + "}\n" +  TABx2 + "catch( Exception e) {}" ); // NOI18N

        if( !lazyMethods ){
            bis.setMethodsSection( sb.toString(), methodCount > 0 ? "}\n" : "  \n" ); // NOI18N
        }
        else{
            bis.setMethodsSection( sb.toString(), TABx2+ "return methods;\n" + TAB + "}\n"); // NOI18N
        }
    }

    /** Regenerates the event set section of BeanInfo */
    private void regenerateEvents() {
        StringBuilder sb = new StringBuilder( 512 );
        int eventCount = 0;

        if ( nullEventSets ) {
            sb.append( TAB + GenerateBeanInfoAction.getString( "COMMENT_NullEventSets" ) );  // NOI18N
            sb.append( TAB + "private static EventSetDescriptor[] eventSets = null;\n" ); // NOI18N
            sb.append( TAB  + "private static EventSetDescriptor[] getEdescriptor(){\n");  // NOI18N
            bis.setEventSetsSection( sb.toString(), TABx2+ "return eventSets;\n" + TAB + "}\n\n" ); // NOI18N
            return;
        }

        sb.append( TAB + GenerateBeanInfoAction.getString("COMMENT_EventSetsIdentifiers") );  // NOI18N

        Set<BiFeature.EventSet> events = new  TreeSet<BiFeature.EventSet>(eventSets);
        for (BiFeature.EventSet bif : events) {
            if ( bif.isIncluded() ) {
                sb.append( TAB + "private static final int " ); // NOI18N
                sb.append( "EVENT_" + bif.getName() ); // NOI18N
                sb.append( " = " + (eventCount++) + ";" ); // NOI18N
                sb.append( "\n" ); // NOI18N
            }
        }

        sb.append( "\n" + TAB + GenerateBeanInfoAction.getString("COMMENT_EventSetsArray"));
        if( !lazyEventSets ){            
            sb.append( TAB + "private static EventSetDescriptor[] eventSets = new EventSetDescriptor[" + // NOI18N
                       eventCount + "];\n\n" ); // NOI18N
            sb.append( TAB  + "private static EventSetDescriptor[] getEdescriptor(){\n");  // NOI18N
            sb.append( TABx2+ "return eventSets;\n" + TAB + "}\n\n" ); // NOI18N
        }
        else{
            //lazy init
            sb.append( TAB + "/*lazy EventSetDescriptor*/\n");    // NOI18N
            sb.append( TAB  + "private static EventSetDescriptor[] getEdescriptor(){\n");  // NOI18N
            sb.append( TABx2+ "EventSetDescriptor[] eventSets = new EventSetDescriptor[");  // NOI18N
            sb.append( eventCount );
            sb.append( "];\n" ); // NOI18N
        }

        if ( eventCount > 0 ){
            if( !lazyEventSets ){
                sb.append( TAB + "static {\n" + TABx2 + "try {\n" ); // NOI18N
            }
            else {
                sb.append( TAB + "\n" + TABx2 + "try {\n" ); // NOI18N
            }
        }

        for (BiFeature.EventSet bif : events) {
            if ( bif.isIncluded() ) {
                // the index prefix MUST be consistent w/ BiFeature.EventSet analyser.
                sb.append( TABx3 + "eventSets[EVENT_" ).append( bif.getName() ).append("] = "); // NOI18N
                sb.append( bif.getCreationString() );
                sb.append(';');
                appendNoi18nText(sb);
                sb.append('\n');

                for (String line : bif.getCustomizationStrings()) {
                    sb.append( TABx3 + "eventSets[EVENT_" ).append( bif.getName() ).append("]."); // NOI18N
                    sb.append( line ).append( ";\n" ); // NOI18N
                }
            }
        }

        if ( eventCount > 0 )
            sb.append( TABx2 + "}\n" +  TABx2 + "catch(IntrospectionException e) {\n" + TABx3 + "e.printStackTrace();\n" + TABx2 + "}" ); // NOI18N

        if( !lazyEventSets ){
            bis.setEventSetsSection( sb.toString(), eventCount > 0 ? "}\n" : "  \n"); // NOI18N
        }
        else{
            bis.setEventSetsSection( sb.toString(), TABx2+ "return eventSets;\n" + TAB + "}\n"); // NOI18N
        }
    }

    /** Generate image icon section */
    private void regenerateIcons() {
        if(  iconBlockRequired() ) {
            StringBuilder sb = new StringBuilder( 200 );

            sb.append( getIconDeclaration( ICONNAME_C16, iconC16 ));
            sb.append( getIconDeclaration( ICONNAME_C32, iconC32 ));
            sb.append( getIconDeclaration( ICONNAME_M16, iconM16 ));
            sb.append( getIconDeclaration( ICONNAME_M32, iconM32 ));

            bis.setIconsSection( sb.toString() );
        }
    }

    private boolean iconBlockRequired(){
        return (iconC16 != null | iconC32 != null | iconM16 != null | iconM32 != null | isIconModified);
    }
    
    private static String getIconDeclaration( String name, String resource ) {
        StringBuilder sb = new StringBuilder( 80 );

        sb.append( TAB + "private static String " ).append( name ).append( " = "); // NOI18N
        if ( resource == null || resource.trim().length() == 0 )
            sb.append( "null;\n"); // NOI18N
        else
            sb.append("\"").append( resource.trim() ).append("\";\n"); // NOI18N
        return sb.toString();
    }

    private void regenerateDefaultIdx() {
        StringBuilder sb = new StringBuilder(100);

        sb.append( TAB + "private static final int " + DEFAULT_PROPERTY_INDEX + " = ").append( defaultPropertyIndex ).append( ";\n"); // NOI18N
        sb.append( TAB + "private static final int " + DEFAULT_EVENT_INDEX + " = ").append( defaultEventIndex ).append( ";\n"); // NOI18N

        bis.setDefaultIdxSection( sb.toString() );
    }

    private void regenerateSuperclass() {
        StringBuilder sb = new StringBuilder(100);
        if( this.isUseSuperClass() ){
            sb.append( TAB + "public BeanInfo[] getAdditionalBeanInfo() {\n");  // NOI18N
            sb.append( TABx2 + "Class superclass = " + classfqn + ".class.getSuperclass();\n");  // NOI18N
            sb.append( TABx2 + "BeanInfo sbi = null;\n");  // NOI18N
            sb.append( TABx2 + "try {\n");  // NOI18N
            sb.append( TABx2 + TAB + "sbi = Introspector.getBeanInfo(superclass);\n");  // NOI18N
                          
            bis.setSuperclassSection( sb.toString(), TABx3 + "}\ncatch(IntrospectionException ex) {\n}\n\nreturn new BeanInfo[] { sbi };\n}\n"); // NOI18N
        }
        else{
            bis.setSuperclassSection( "\n", "\n");  // NOI18N
        }
    }

    /** Analyzes existing BeanInfo */
    private void analyzeBeanInfoSource() throws GenerateBeanException {

        if ( !bis.isNbBeanInfo() )
            return;

        String section = bis.getIconsSection();
        List<String> code = normalizeText( section );
        setIconsFromBeanInfo( code );

        section = bis.getDefaultIdxSection();
        code = normalizeText( section );
        setDefaultIdxFromBeanInfo( code );

        section = bis.getDescriptorSection();
        code = normalizeText( section );
        nullDescriptor = setPropertiesFromBeanInfo( descriptor, code, "BeanDescriptor" ); // NOI18N
        if ( !nullDescriptor ){
            setLazyDescriptor( isLazy( code, "BeanDescriptor" ) ); // NOI18N
        }
        
        section = bis.getPropertiesSection();
        code = normalizeText( section );
        nullProperties = setPropertiesFromBeanInfo( properties, code, "PropertyDescriptor[]" ); // NOI18N
        if ( !nullProperties ){
            setLazyProperties( isLazy( code, "PropertyDescriptor" ) ); // NOI18N
            setPropertiesFromBeanInfo( idxProperties, code, "PropertyDescriptor[]" ); // NOI18N
        }
        
        section = bis.getMethodsSection();
        if (section == null) {
            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(GenerateBeanInfoAction.getString("MSG_Old_Version"), NotifyDescriptor.WARNING_MESSAGE));  // NOI18N
            nullMethods = true;
        } else {
            code = normalizeText(section);
            nullMethods = setPropertiesFromBeanInfo(methods, code, "MethodDescriptor[]"); // NOI18N
            if( !nullMethods ){
                setLazyMethods( isLazy( code, "MethodDescriptor" ) ); // NOI18N
            }
        }

        section = bis.getEventSetsSection();
        code = normalizeText( section );
        nullEventSets = setPropertiesFromBeanInfo( eventSets, code, "EventSetDescriptor[]" ); // NOI18N
        if( !nullEventSets ){
            setLazyEventSets( isLazy( code, "EventSetDescriptor" ) ); // NOI18N
        }

        section = bis.getSuperclassSection();
        code = normalizeText( section );
        setUseSuperClass(hasSuperClass(code));
    }

    /** "Normalizes" the JavaCode. Removes all unneeded whitespaces. Makes strings from
     * commands. 
     * @param code String containg the java source code
     * @return Normalized code as collection of string.
     */
    static List<String> normalizeText( String code ) {

        List<String> result = new  ArrayList<String>();
        StringBuilder sb = new StringBuilder( 100 );

        final int IN_TEXT = 0;
        final int IN_WHITE = 1;
        int mode = IN_WHITE;
        final int noi18n_length = NOI18N_COMMENT.length();
        boolean eo_javaid = false;
        boolean guarded = false;    //guarded beetwen ""
        boolean escape = false;    //guarded beetwen ""
        
        for ( int i = 0; code != null && i < code.length(); i++ ) {
            char ch = code.charAt( i );
            
            if( ch != '\"' )
                escape = false;
            
            switch ( mode ) {
            case IN_TEXT:
                if ( !Character.isWhitespace( ch ) ) {
                    if ( ch == ';' ) {
                        sb.append( ch );
                        // check if there is "NOI18N" comment appended
                        if (i + noi18n_length < code.length() && NOI18N_COMMENT.equals(code.substring(i + 1, i + 1 + noi18n_length))) {
                            sb.append(NOI18N_COMMENT);
                            i += noi18n_length;
                        }
                        result.add( sb.toString() );
                        sb.setLength( 0 );
                        mode = IN_WHITE;
                        eo_javaid = false;
                    }
                    else if ( ch == '\\' ){
                        escape = true;
                        sb.append( ch );
                    }
                    else if ( ch == '\"' ){
                        if( !escape )
                            guarded = !guarded;
                        escape = false;
                        sb.append( ch );
                    }
                    else    
                        sb.append( ch );
                }
                else {
                    if( guarded )
                        sb.append( ch );
                    else{
                        char prevch = code.charAt( i - 1 );
                        eo_javaid = Character.isJavaIdentifierPart ( prevch )
                                || prevch == ']';
                        mode = IN_WHITE;
                    }
                }
                break;
            case IN_WHITE:
                if ( !Character.isWhitespace( ch ) ) {
                    if ( eo_javaid && Character.isJavaIdentifierStart ( ch ) )
                        sb.append( ' ' );
                    else if ( ch == '\\' ){
                        escape = true;
                        sb.append( ch );
                    }
                    else if ( ch == '\"' ) {
                        if( !escape )
                            guarded = !guarded;
                        escape = false;
                    }
                    sb.append( ch );
                    mode = IN_TEXT;                    
                }
                break;
            }
        }
        
        if (sb.length() > 0) result.add(sb.toString());
        
        return result;

    }

    static String[] getParameters( String command ) {
        String paramString;

        int beg = command.indexOf( '(' );
        int end = command.lastIndexOf( ')' );

        if ( beg != -1 && end != -1 && ( ++beg < end ) )
            paramString = command.substring( beg, end );
        else
            return new String[0];

        StringTokenizer strTok = new StringTokenizer( paramString, "," ); // NOI18N

        String[] resultStrs = new String[ strTok.countTokens() ];

        for ( int i = 0; strTok.hasMoreTokens(); i++ )
            resultStrs[i] = strTok.nextToken();

        return resultStrs;
    }

    static String getArgumentParameter( String command ) {
        int beg = command.indexOf( '(' );
        int end = command.lastIndexOf( ')' );

        if ( beg != -1 && end != -1 && ( ++beg < end ) )
            return command.substring( beg, end );
        else
            return null;
    }
    
    /** Gets the initializer */
    static String getInitializer( String command ) {

        int beg = command.lastIndexOf( '=' );
        int end = command.lastIndexOf( ';' );

        if ( beg != -1 && end != -1 && ( ++beg < end ) )
            return command.substring( beg, end ).trim();
        else
            return null;
    }

    /** test if initializer is lazy */    
    static boolean isLazy( List<String> code, String name ) {
        for (String statement : code) {
            if ( statement.indexOf( name ) != -1 ){
                if( statement.indexOf( "/*lazy " + name + "*/" ) != -1 ){  // NOI18N
                    return true;
                }
            }
        }
        return false;
    }

    static boolean hasSuperClass( List<String> code ) {
        for (String statement : code) {
            //System.out.println(statement);
            if ( statement.indexOf( "public BeanInfo[] getAdditionalBeanInfo()" ) != -1 ){  // NOI18N
                    return true;
            }
        }
        return false;
    }

    /** Removes Quotation marks */
    static String removeQuotation( String text ) {

        int beg = text.indexOf( '"' );
        int end = text.lastIndexOf( '"' );

        if ( beg != -1 && end != -1 && ( ++beg < end ) )
            return text.substring( beg, end );
        else
            return null;
    }


    /** Let's the collection of features check for it's properties in BeanInfo */
    boolean setPropertiesFromBeanInfo( List<? extends BiFeature> features, List<String> code, String name ) throws GenerateBeanException {
        for (String statement : code) {
            if ( statement.indexOf( name ) != -1 )
                
                if ( "null".equals(getInitializer( statement ))  ){ // NOI18N 
                    return true;
                }
                else
                    break;  //others f.e. null/*lazy*/
        }

        for (BiFeature bif : features) {
            bif.setBrackets(bif.getBrackets());
            bif.analyzeCustomization( code );            
        }

        return false;
    }

    /** Analyze icons properties from bean info */

    void setIconsFromBeanInfo ( List<String> code ) {
        for (String statement : code) {
            if ( statement.indexOf( ICONNAME_C16 ) != -1 ) {
                iconC16 = removeQuotation( getInitializer( statement ) );
                continue;
            }
            if ( statement.indexOf( ICONNAME_C32 ) != -1 ) {
                iconC32 = removeQuotation( getInitializer( statement ) );
                continue;
            }
            if ( statement.indexOf( ICONNAME_M16 ) != -1 ) {
                iconM16 = removeQuotation( getInitializer( statement ) );
                continue;
            }
            if ( statement.indexOf( ICONNAME_M32 ) != -1 ) {
                iconM32 = removeQuotation( getInitializer( statement ) );
                continue;
            }
        }
    }


    /** Analyze default section  */

    void setDefaultIdxFromBeanInfo( List<String> code ) {
        for (String statement : code) {
            if ( statement.indexOf( DEFAULT_PROPERTY_INDEX ) != -1 ) {
                try {
                    defaultPropertyIndex = Integer.parseInt( getInitializer( statement ) );
                }
                catch ( java.lang.NumberFormatException e ) {
                    defaultPropertyIndex = -1;
                }

                continue;
            }
            if ( statement.indexOf( DEFAULT_EVENT_INDEX ) != -1 ) {
                try {
                    defaultEventIndex = Integer.parseInt( getInitializer( statement ) );
                }
                catch ( java.lang.NumberFormatException e ) {
                    defaultEventIndex = -1;
                }

                continue;
            }

        }
    }
    
    private void appendNoi18nText(StringBuilder sb) {
        sb.append(NOI18N_COMMENT);
    }
    
    void setIconModified() {
        if (isUpdateMode && !isIconModified) {
            isIconModified = true;
            setModified();
        }
    }

    void setModified() {
        if (isUpdateMode && !isModified) {
            this.isModified = true;
            BIEditorSupport editor = this.bis.getDataObject().getLookup().lookup(BIEditorSupport.class);
            editor.notifyModified();
        }
    }
    
    public boolean isModified() {
        return this.isModified;
    }

    public boolean isBeanBroken() {
        return isBeanBroken;
    }
    
}
