/* ====================================================================
*
* The ObjectStyle Group Software License, Version 1.0
*
* Copyright (c) 2002 The ObjectStyle Group
* and individual authors of the software. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* ObjectStyle Group (http://objectstyle.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "ObjectStyle Group" and "Cayenne"
* must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact andrus@objectstyle.org.
*
* 5. Products derived from this software may not be called "ObjectStyle"
* nor may "ObjectStyle" appear in their names without prior written
* permission of the ObjectStyle Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the ObjectStyle Group. For more
* information on the ObjectStyle Group, please see
* <http://objectstyle.org/>.
*
*/
package org.objectstyle.woenvironment.pb;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.objectstyle.woenvironment.pb.PBXProject.ObjectsTable.ID;
import org.objectstyle.woenvironment.plist.PropertyListParserException;
import org.objectstyle.woenvironment.plist.WOLPropertyListSerialization;
/**
* A <b>PBXProject</b> represents a Project Builder X project package (<code>*.pbproj</code>).
*
* @author Jonathan 'Wolf' Rentzsch
* @author Anjo Krank
*/
@SuppressWarnings("unchecked")
public class PBXProject {
public void addSourceReference(String path) {
if (!_sourceRefs.contains(path))
_sourceRefs.add(path);
}
public void removeSourceReference(String path) {
_sourceRefs.remove(path);
}
public void addResourceFileReference(String path) {
if (!_resourceFileRefs.contains(path))
_resourceFileRefs.add(path);
}
public void removeResourceFileReference(String path) {
_resourceFileRefs.remove(path);
}
public void addResourceFolderReference(String path) {
if (!_resourceFolderRefs.contains(path))
_resourceFolderRefs.add(path);
}
public void removeResourceFolderReference(String path) {
_resourceFolderRefs.remove(path);
}
public void addWSResourceFileReference(String path) {
if (!_wsresourceFileRefs.contains(path))
_wsresourceFileRefs.add(path);
}
public void removeWSResourceFileReference(String path) {
_wsresourceFileRefs.remove(path);
}
public void addWSResourceFolderReference(String path) {
if (!_wsresourceFolderRefs.contains(path))
_wsresourceFolderRefs.add(path);
}
public void removeWSResourceFolderReference(String path) {
_wsresourceFolderRefs.remove(path);
}
public void addFrameworkReference(String path) {
if (!_frameworkRefs.contains(path))
_frameworkRefs.add(path);
}
public void removeFrameworkReference(String path) {
_frameworkRefs.remove(path);
}
public void save(File projectFile) throws PropertyListParserException, IOException {
ObjectsTable objectsTable = new ObjectsTable();
String projectPath;
try {
projectPath = projectFile.getCanonicalPath();
} catch (IOException ex) {
throw new RuntimeException("Can't get path of project file: " + projectFile);
}
int last = projectPath.lastIndexOf(File.separator);
if (last != -1) {
projectPath = projectPath.substring(0, last);
}
last = projectPath.lastIndexOf(File.separator);
if (last != -1) {
projectPath = projectPath.substring(0, last);
}
ArrayList sourceFileIDs = new ArrayList();
ArrayList resourceFileIDs = new ArrayList();
ArrayList wsresourceFileIDs = new ArrayList();
ArrayList frameworkFileIDs = new ArrayList();
// Walk refs, creating the appropriate reference type and an associated
// build file for each and adding it to the objects table and group
// children list.
String path;
Iterator it;
it = _sourceRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
if (path.indexOf(projectPath) == 0) {
path = path.substring(projectPath.length() + 1);
}
File file = new File(path);
Map reference = newFileReference(file.getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
sourceFileIDs.add(refID);
}
it = _resourceFileRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
if (path.indexOf(projectPath) == 0) {
path = path.substring(projectPath.length() + 1);
}
File file = new File(path);
if (!_resourceFolderRefs.contains(projectPath + "/" + file.getParent())) {
Map reference = newFileReference(file.getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
resourceFileIDs.add(refID);
}
}
it = _resourceFolderRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
if (path.indexOf(projectPath) == 0) {
path = path.substring(projectPath.length() + 1);
}
File file = new File(path);
Map reference = newFolderReference(file.getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
resourceFileIDs.add(refID);
}
it = _wsresourceFileRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
if (path.indexOf(projectPath) == 0) {
path = path.substring(projectPath.length() + 1);
}
File file = new File(path);
if (!_wsresourceFolderRefs.contains(projectPath + "/" + file.getParent())) {
Map reference = newFileReference(file.getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
wsresourceFileIDs.add(refID);
}
}
it = _wsresourceFolderRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
if (path.indexOf(projectPath) == 0) {
path = path.substring(projectPath.length() + 1);
}
File file = new File(path);
Map reference = newFolderReference(file.getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
wsresourceFileIDs.add(refID);
}
it = _frameworkRefs.iterator();
while (it.hasNext()) {
path = (String) it.next();
Map reference = newFrameworkReference((new File(path)).getName(), path);
ObjectsTable.ID refID = objectsTable.insert(reference);
frameworkFileIDs.add(refID);
}
ArrayList childrenIDs = new ArrayList();
childrenIDs.add(objectsTable.insert(newGroup("Sources", sourceFileIDs)));
childrenIDs.add(objectsTable.insert(newGroup("Resources", resourceFileIDs)));
childrenIDs.add(objectsTable.insert(newGroup("Web Server", wsresourceFileIDs)));
childrenIDs.add(objectsTable.insert(newGroup("Frameworks", frameworkFileIDs)));
// Create the PBXGroup and add it to the objects table.
ObjectsTable.ID mainGroupID = objectsTable.insert(newGroup("Root", childrenIDs));
// Create the PBXToolTarget and add it to the objects table and
// the targets list.
ArrayList targetIDs = new ArrayList(2);
List phases;
phases = newBuildPhaseList(sourceFileIDs, resourceFileIDs, wsresourceFileIDs, frameworkFileIDs, objectsTable);
Map appServerTarget = newAppServerTarget(phases, objectsTable);
phases = newBuildPhaseList(Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, objectsTable);
Map antTarget = newAntTarget(phases, objectsTable);
phases = newBuildPhaseList(Collections.EMPTY_LIST, Collections.EMPTY_LIST, wsresourceFileIDs, Collections.EMPTY_LIST, objectsTable);
Map webServerTarget = newWebServerTarget(phases, objectsTable);
targetIDs.add(objectsTable.insert(appServerTarget));
targetIDs.add(objectsTable.insert(antTarget));
targetIDs.add(objectsTable.insert(webServerTarget));
// Create the PBXProject and add it to the objects table.
ObjectsTable.ID projectID = objectsTable.insert(newProject(mainGroupID, targetIDs, objectsTable));
// Create the root dictionary.
Map pbxproj = newPBXProj(objectsTable, projectID);
WOLPropertyListSerialization.propertyListToFile("", projectFile, pbxproj);
}
protected List newBuildFileList(List fileIDs, ObjectsTable objectsTable) {
List result = new ArrayList(fileIDs.size());
for (Iterator iter = fileIDs.iterator(); iter.hasNext();) {
ObjectsTable.ID refID = (ObjectsTable.ID) iter.next();
result.add(objectsTable.insert(newBuildFile(refID)));
}
return result;
}
protected List newBuildPhaseList(List sourceFileIDs, List resourceFileIDs, List wsresourceFileIDs, List frameworkFileIDs, ObjectsTable objectsTable) {
ArrayList buildPhaseIDs = new ArrayList(4);
if (hasBuildPhases()) {
buildPhaseIDs.add(objectsTable.insert(newSourcesBuildPhase(newBuildFileList(sourceFileIDs, objectsTable))));
buildPhaseIDs.add(objectsTable.insert(newResourcesBuildPhase(newBuildFileList(resourceFileIDs, objectsTable))));
buildPhaseIDs.add(objectsTable.insert(newWSResourcesBuildPhase(newBuildFileList(wsresourceFileIDs, objectsTable))));
buildPhaseIDs.add(objectsTable.insert(newFrameworkBuildPhase(newBuildFileList(frameworkFileIDs, objectsTable))));
}
return buildPhaseIDs;
}
// ---------------------------------------------------------------
// Implementation stuff.
protected ArrayList _resourceFolderRefs = new ArrayList();
protected ArrayList _resourceFileRefs = new ArrayList();
protected ArrayList _wsresourceFolderRefs = new ArrayList();
protected ArrayList _wsresourceFileRefs = new ArrayList();
protected ArrayList _frameworkRefs = new ArrayList();
protected ArrayList _sourceRefs = new ArrayList();
protected Map map(Object[] keyValues) {
Map result = new TreeMap();
for (int i = 0; i < keyValues.length; i += 2) {
result.put(keyValues[i], keyValues[i + 1]);
}
return result;
}
protected Map newFileReference(String name, String path) {
return map(new Object[] { "isa", "PBXFileReference", "refType", (new File(path)).isAbsolute() ? "0" : "2", "name", name, "path", path });
}
protected Map newFolderReference(String name, String path) {
return map(new Object[] { "isa", "PBXFolderReference", "refType", (new File(path)).isAbsolute() ? "0" : "2", "name", name, "path", path });
}
protected Map newFrameworkReference(String name, String path) {
return map(new Object[] { "isa", "PBXFrameworkReference", "refType", "0", "name", name, "path", path });
}
protected Map newBuildFile(ObjectsTable.ID fileRefID) {
return map(new Object[] { "isa", "PBXBuildFile", "fileRef", fileRefID });
}
protected Map newFrameworkBuildPhase(List buildFileIDs) {
return map(new Object[] { "isa", "PBXFrameworksBuildPhase", "files", buildFileIDs });
}
protected Map newResourcesBuildPhase(List buildFileIDs) {
return map(new Object[] { "isa", "PBXResourcesBuildPhase", "files", buildFileIDs });
}
protected Map newWSResourcesBuildPhase(List buildFileIDs) {
return map(new Object[] { "isa", "PBXResourcesBuildPhase", "files", buildFileIDs });
}
protected Map newSourcesBuildPhase(List buildFileIDs) {
return map(new Object[] { "isa", "PBXSourcesBuildPhase", "files", buildFileIDs });
}
protected Map newGroup(String name, List childrenIDs) {
return map(new Object[] { "isa", "PBXGroup", "refType", "4", "name", name, "children", childrenIDs });
}
protected Map newAppServerTarget(List buildPhaseIDs, ObjectsTable objectsTable) {
return map(new Object[] { "isa", "PBXToolTarget", "buildSettings", new HashMap(), "name", "Application Server", "buildPhases", buildPhaseIDs });
}
protected Map newWebServerTarget(List buildPhaseIDs, ObjectsTable objectsTable) {
return map(new Object[] { "isa", "PBXToolTarget", "buildSettings", new HashMap(), "name", "Web Server", "buildPhases", buildPhaseIDs });
}
protected Map newAntTarget(List buildPhaseIDs, ObjectsTable objectsTable) {
Map result = map(new Object[] { "isa", "PBXLegacyTarget", "buildArgumentsString", "-emacs $(ACTION)", "buildSettings", new HashMap(), "buildToolPath", "/Developer/Java/Ant/bin/ant", "passBuildSettingsInEnvironment", "1", "name", "Ant", "buildPhases", buildPhaseIDs });
List<ID> buildConfigurations = new LinkedList<ID>();
buildConfigurations.add(objectsTable.insert(newBuildConfiguration(map(new Object[] { "COPY_PHASE_STRIP", "NO" }), "Debug")));
buildConfigurations.add(objectsTable.insert(newBuildConfiguration(map(new Object[] { "COPY_PHASE_STRIP", "YES" }), "Release")));
buildConfigurations.add(objectsTable.insert(newBuildConfiguration(new HashMap(), "Default")));
result.put("buildConfigurationList", objectsTable.insert(newBuildConfigurationList(buildConfigurations, false, "Default")));
result.put("productName", "Ant");
return result;
}
protected Map newBuildConfigurationList(List _buildConfigurations, boolean _defaultConfigurationIsVisible, String _defaultConfigurationName) {
Map buildConfigurationList = map(new Object[] { "buildConfigurations", _buildConfigurations, "defaultConfigurationIsVisible", (_defaultConfigurationIsVisible) ? "1" : "0", "defaultConfigurationName", _defaultConfigurationName, "isa", "XCConfigurationList" });
return buildConfigurationList;
}
protected Map newBuildStyle(Map _buildSettings, String _name) {
return newBuildStyleOrConfiguration(_buildSettings, "PBXBuildStyle", _name);
}
// AK: this might be only for XCode and up
protected Map newBuildConfiguration(Map _buildSettings, String _name) {
return newBuildStyleOrConfiguration(_buildSettings, "XCBuildConfiguration", _name);
}
protected Map newBuildStyleOrConfiguration(Map _buildSettings, String _isa, String _name) {
return map(new Object[] { "buildSettings", _buildSettings, "isa", _isa, "name", _name });
}
protected Map newProject(ObjectsTable.ID groupID, List targetIDs, ObjectsTable objectsTable) {
return map(new Object[] { "isa", "PBXProject", "hasScannedForEncodings", "1", "projectDirPath", ".", "mainGroup", groupID, "targets", targetIDs });
}
protected Map newPBXProj(Map objectsTable, ObjectsTable.ID rootObject) {
return map(new Object[] { "archiveVersion", "1", "objectVersion", "38", "rootObject", rootObject, "objects", objectsTable });
}
protected boolean hasBuildPhases() {
return true;
}
protected static class ObjectsTable extends TreeMap {
public static class ID extends Number {
int _value;
protected ID(int value) {
_value = value;
}
@Override
public String toString() {
String hexValue = Integer.toHexString(_value);
StringBuffer result = new StringBuffer(24);
for (int i = 24 - hexValue.length(); i > 0; i--) {
result.append('0');
}
result.append(hexValue);
return result.toString();
}
// Bogus Number abstract class implementation requirements
// follow. We have to be a Number since
// PropertyListSerialization wouldn't otherwise stream a custom
// class -- it only knows Lists, Maps, Strings and Numbers.
// Strings are final, so that pretty much leaves us with Number,
// as with the container classes PropertyListSerialization
// would attempt to look into us, which isn't the right thing
// to do.
@Override
public double doubleValue() {
return 0;
}
@Override
public float floatValue() {
return 0;
}
@Override
public int intValue() {
return 0;
}
@Override
public long longValue() {
return 0;
}
@Override
public short shortValue() {
return 0;
}
}
public ObjectsTable() {
super(new Comparator() {
public int compare(Object a, Object b) {
if (a == b)
return 0;
if (a == null)
return -1;
if (b == null)
return 1;
return a.toString().compareTo(b.toString());
}
});
}
public ID insert(Object object) {
ID id = new ID(_unique++);
put(id, object);
return id;
}
protected int _unique = 1;
}
}