/*******************************************************************************
 * Copyright (c) 2008-2010 Sonatype, 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:
 *      Sonatype, Inc. - initial API and implementation
 *******************************************************************************/

package org.bndtools.builder.classpath;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;

import aQute.lib.io.IO;

class ClasspathContainerSerializationHelper<C extends IClasspathContainer> {
    C readClasspathContainer(File file) throws IOException, ClassNotFoundException {
        try (InputStream in = IO.stream(file)) {
            return readClasspathContainer(in);
        }
    }

    C readClasspathContainer(InputStream in) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectResolver(in)) {
            @SuppressWarnings("unchecked")
            C container = (C) ois.readObject();
            return container;
        }
    }

    private static final class ObjectResolver extends ObjectInputStream {
        ObjectResolver(InputStream in) throws IOException {
            super(in);
            enableResolveObject(true);
        }

        @Override
        protected Object resolveObject(Object o) throws IOException {
            if (o instanceof ProjectEntryReplace) {
                return ((ProjectEntryReplace) o).getEntry();
            } else if (o instanceof LibraryEntryReplace) {
                return ((LibraryEntryReplace) o).getEntry();
            } else if (o instanceof ClasspathAttributeReplace) {
                return ((ClasspathAttributeReplace) o).getAttribute();
            } else if (o instanceof AccessRuleReplace) {
                return ((AccessRuleReplace) o).getAccessRule();
            } else if (o instanceof PathReplace) {
                return ((PathReplace) o).getPath();
            }
            return super.resolveObject(o);
        }
    }

    void writeClasspathContainer(C container, File file) throws IOException {
        try (OutputStream out = IO.outputStream(file)) {
            writeClasspathContainer(container, out);
        }
    }

    void writeClasspathContainer(C container, OutputStream out) throws IOException {
        try (ObjectOutputStream oos = new ObjectReplacer(out)) {
            oos.writeObject(container);
        }
    }

    private static final class ObjectReplacer extends ObjectOutputStream {
        ObjectReplacer(OutputStream out) throws IOException {
            super(out);
            enableReplaceObject(true);
        }

        @Override
        protected Object replaceObject(Object o) throws IOException {
            if (o instanceof IClasspathEntry) {
                IClasspathEntry e = (IClasspathEntry) o;
                if (e.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
                    return new ProjectEntryReplace(e);
                } else if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
                    return new LibraryEntryReplace(e);
                }
            } else if (o instanceof IClasspathAttribute) {
                return new ClasspathAttributeReplace((IClasspathAttribute) o);
            } else if (o instanceof IAccessRule) {
                return new AccessRuleReplace((IAccessRule) o);
            } else if (o instanceof IPath) {
                return new PathReplace((IPath) o);
            }
            return super.replaceObject(o);
        }
    }

    /**
     * A library IClasspathEntry replacement used for object serialization
     */
    static final class LibraryEntryReplace implements Serializable {
        private static final long serialVersionUID = 1L;

        private final IPath path;
        private final IPath sourceAttachmentPath;
        private final IPath sourceAttachmentRootPath;
        private final IAccessRule[] accessRules;
        private final IClasspathAttribute[] extraAttributes;
        private final boolean exported;

        LibraryEntryReplace(IClasspathEntry entry) {
            this.path = entry.getPath();
            this.sourceAttachmentPath = entry.getSourceAttachmentPath();
            this.sourceAttachmentRootPath = entry.getSourceAttachmentRootPath();
            this.accessRules = entry.getAccessRules();
            this.extraAttributes = entry.getExtraAttributes();
            this.exported = entry.isExported();
        }

        IClasspathEntry getEntry() {
            return JavaCore.newLibraryEntry(path, sourceAttachmentPath, sourceAttachmentRootPath, accessRules, extraAttributes, exported);
        }
    }

    /**
     * A project IClasspathEntry replacement used for object serialization
     */
    static final class ProjectEntryReplace implements Serializable {
        private static final long serialVersionUID = 1L;

        private final IPath path;
        private final IAccessRule[] accessRules;
        private final boolean combineAccessRules;
        private final IClasspathAttribute[] extraAttributes;
        private final boolean exported;

        ProjectEntryReplace(IClasspathEntry entry) {
            this.path = entry.getPath();
            this.accessRules = entry.getAccessRules();
            this.combineAccessRules = entry.combineAccessRules();
            this.extraAttributes = entry.getExtraAttributes();
            this.exported = entry.isExported();
        }

        IClasspathEntry getEntry() {
            return JavaCore.newProjectEntry(path, accessRules, combineAccessRules, extraAttributes, exported);
        }
    }

    /**
     * An IClasspathAttribute replacement used for object serialization
     */
    static final class ClasspathAttributeReplace implements Serializable {
        private static final long serialVersionUID = 1L;

        private final String name;
        private final String value;

        ClasspathAttributeReplace(IClasspathAttribute attribute) {
            this.name = attribute.getName();
            this.value = attribute.getValue();
        }

        IClasspathAttribute getAttribute() {
            return JavaCore.newClasspathAttribute(name, value);
        }
    }

    /**
     * An IAccessRule replacement used for object serialization
     */
    static final class AccessRuleReplace implements Serializable {
        private static final long serialVersionUID = 1L;

        private final IPath pattern;
        private final int kind;

        AccessRuleReplace(IAccessRule accessRule) {
            pattern = accessRule.getPattern();
            kind = accessRule.getKind() | (accessRule.ignoreIfBetter() ? IAccessRule.IGNORE_IF_BETTER : 0);
        }

        IAccessRule getAccessRule() {
            return JavaCore.newAccessRule(pattern, kind);
        }
    }

    /**
     * An IPath replacement used for object serialization
     */
    static final class PathReplace implements Serializable {
        private static final long serialVersionUID = 1L;

        private final String path;

        PathReplace(IPath path) {
            this.path = path.toPortableString();
        }

        IPath getPath() {
            return Path.fromPortableString(path);
        }
    }
}
