blob: 608c58344d89bf97edd1f7aac9a4cb38b374f702 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 21.08.2011 Aaron Digulla.
* 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:
* Aaron Digulla - initial API and implementation and/or initial documentation
*******************************************************************************/
package m4e
import de.pdark.decentxml.*
class PomElement {
Pom pom
Element xml
String value( TextNode node ) {
Element e = xml( node )
return e == null ? null : e.trimmedText
}
void value( TextNode node, String text ) {
Element e = xml( node )
if( e == null ) {
if( !text ) {
return
}
e = PomUtils.getOrCreate( xml, node.name )
e.text = text
} else {
if( !text ) {
PomUtils.removeWithIndent( e )
} else {
e.text = text
}
}
}
List list( ListNode node ) {
def result = xml.getChild( node.name )?.getChildren( node.child ).collect {
new PomElement( pom: this.pom, xml: it )
}
return result ?: []
}
Element xml( node ) {
if( !(node instanceof String ) ) {
node = node.name
}
return xml.getChild( node )
}
PomElement element( node ) {
Element child = xml( node )
return child ? new PomElement( xml: child, pom: pom ) : null
}
void remove() {
PomUtils.removeWithIndent( xml )
}
}
class TextNode {
String name
String defaultValue
}
class ListNode {
String name
String child
}
class Pom extends PomElement {
final static TextNode GROUP_ID = new TextNode( name: 'groupId' )
final static TextNode ARTIFACT_ID = new TextNode( name: 'artifactId' )
final static TextNode VERSION = new TextNode( name: 'version' )
final static TextNode PARENT = new TextNode( name: 'parent' )
final static TextNode PROPERTIES = new TextNode( name: 'properties' )
final static String IMPORT_PACKAGE_PROPERTY = 'mt4e.osgi.importPackage'
final static String EXPORT_PACKAGE_PROPERTY = 'mt4e.osgi.exportPackage'
final static String IS_SINGLETON_PROPERTY = 'mt4e.osgi.isSingleton'
String source
String key() {
return "${groupId()}:${artifactId()}:${version()}";
}
String shortKey() {
return "${groupId()}:${artifactId()}";
}
String groupId() {
String groupId = value( GROUP_ID )
if( null == groupId ) {
PomElement parent = element( PARENT )
groupId = parent.value( GROUP_ID )
}
return groupId
}
String artifactId() {
return value( ARTIFACT_ID )
}
String version() {
String version = value( VERSION )
if( null == version ) {
PomElement parent = element( PARENT )
version = parent.value( VERSION )
}
return version
}
public static Pom load( def input ) {
String source
if( input instanceof String ) {
source = "<String>"
input = new XMLStringSource( input )
}
if( input instanceof File ) {
source = input.absolutePath
input = new XMLIOSource( input )
}
XMLParser parser = new XMLParser();
def doc = parser.parse( input )
def pom = new Pom( doc: doc, source: source )
pom.init()
return pom
}
Document doc;
void init() {
this.pom = this
this.xml = doc.getRootElement()
assert 'project' == this.xml.name
}
List<String> files() {
File dir = new File( source ).parentFile
String prefix = "${artifactId()}-${version()}"
Set<String> files = new TreeSet()
dir.eachFile() { File item ->
String name = item.name
if( !name.startsWith( prefix ) || name.endsWith( '.bak' ) ) {
return
}
name = name.substring( prefix.size() )
if( name.startsWith( '.' ) || name.startsWith( '-' ) ) {
name = name.substring( 1 )
}
name = name.removeEnd( '.sha1' )
name = name.removeEnd( '.md5' )
name = name.removeEnd( '.jar' )
files << name
}
return new ArrayList( files )
}
void save( File file ) {
String path = file.absolutePath
File tmp = new File( path + '.tmp' )
File bak = new File( path + '.bak' )
XMLWriter writer = new XMLWriter( new OutputStreamWriter( new FileOutputStream( tmp ), doc.encoding ?: 'utf-8' ) )
try {
doc.toXML( writer )
} finally {
writer.close()
}
if( bak.exists() ) {
bak.usefulDelete()
}
if( file.exists() ) {
file.usefulRename( bak )
}
tmp.usefulRename( file )
}
String toString() {
return doc.toXML()
}
static final ListNode DEPENDENCIES = new ListNode( name: 'dependencies', child: 'dependency' )
List<Dependency> getDependencies() {
return list( DEPENDENCIES ).collect() {
Dependency.wrap( it )
}
}
Profile profile( String name ) {
def profiles = xml.getChild( 'profiles' )
if( ! profiles ) {
return null
}
def profile = profiles.getChildren( 'profile' ).find {
def id = it.getChild( 'id' )
return id.text == name
}
// println "name=${name} profile=${profile}"
return profile == null ? null : new Profile( xml: profile, pom: this )
}
Profile getOrCreateProfile( String name ) {
def profile = profile( name )
if( profile ) {
return new Profile( xml: profile, pom: this )
}
def profiles = PomUtils.getOrCreate( xml, 'profiles' )
return profile == null ? createNewProfile( profiles, name ) : profile
}
private Profile createNewProfile( Element profiles, String name ) {
def xml = new Element( 'profile' )
profiles.addNode( xml )
def id = PomUtils.getOrCreate( xml, 'id' )
id.text = name
def profile = new Profile( xml: xml, pom: this )
profile.activeByDefault( false )
PomUtils.getOrCreate( xml, 'dependencies' )
return profile
}
}
class PomUtils {
static void removeWithIndent( Element e ) {
if( !e || !e.parentElement ) {
return
}
Element parent = e.parentElement
int index = parent.nodeIndexOf( e )
while( index > 0 ) {
index --
Node previous = parent.getNode( index )
if( ! XMLUtils.isText( previous ) ) {
break
}
parent.removeNode( index )
}
e.remove()
}
static int getLevel( Element e ) {
int level = 0
while( e ) {
e = e.getParentElement()
level ++
}
return -- level
}
static Element getOrCreate( Element e, String name ) {
Element child = e.getChild( name )
if( !child ) {
child = new Element( name )
e.addNode( child )
}
return child
}
}
class Dependency extends PomElement {
final static TextNode GROUP_ID = Pom.GROUP_ID
final static TextNode ARTIFACT_ID = Pom.ARTIFACT_ID
final static TextNode VERSION = Pom.VERSION
final static TextNode CLASSIFIER = new TextNode( name: 'classifier' )
final static TextNode TYPE = new TextNode( name: 'type', defaultValue: 'jar' )
final static TextNode SCOPE = new TextNode( name: 'scope', defaultValue: 'compile' )
final static TextNode OPTIONAL = new TextNode( name: 'optional', defaultValue: 'false' )
@Override
public String toString() {
return "Dependency( ${key()} )";
}
String key() {
return "${value( GROUP_ID )}:${value( ARTIFACT_ID )}:${value( VERSION )}";
}
String shortKey() {
return "${value( GROUP_ID )}:${value( ARTIFACT_ID )}";
}
static Dependency wrap( PomElement e ) {
if( e instanceof Dependency ) {
return e
}
return new Dependency( xml: e.xml, pom: e.pom )
}
}
class Profile extends PomElement {
static final ListNode DEPENDENCIES = Pom.DEPENDENCIES
List<Dependency> getDependencies() {
return list( DEPENDENCIES ).collect() {
Dependency.wrap( it )
}
}
void addDependency( Dependency d ) {
d.remove()
xml( DEPENDENCIES ).addNode( d.xml )
}
void activeByDefault( boolean value ) {
def activation = PomUtils.getOrCreate( xml, 'activation' )
def activeByDefault = PomUtils.getOrCreate( activation, 'activeByDefault')
activeByDefault.text = Boolean.toString( value )
}
void cleanUp() {
def activation = xml.getChild( 'activation' )
if( null == activation ) {
return
}
def activeByDefault = activation.getChild( 'activeByDefault' )
if( null == activeByDefault || activeByDefault.text == 'true' ) {
return
}
PomUtils.removeWithIndent( activation )
}
String getId() {
def id = xml.getChild( 'id' )
return id ? id.text : null
}
@Override
public String toString() {
return "Profile( ${id} )";
}
}
class XmlFormatter {
Pom pom
private String indentStep = ' '
static void format( Pom pom ) {
new XmlFormatter( pom: pom ).format()
}
void format() {
pom.xml.getChild( 'profiles' )?.getChildren( 'profile' ).each {
Profile p = new Profile( xml: it, pom: pom )
p.cleanUp()
}
format( pom.xml )
}
void format( Element e ) {
int level = PomUtils.getLevel( e ) + 1
String indent = '\n' + indentStep * level
int N = e.nodeCount()
// println "level=${level} N=${N} ${e.name}"
// Must work backwards because indentElement() adds new nodes
for( int i=N-1; i>=0; i-- ) {
Node n = e.getNode( i )
if( XMLUtils.isElement( n ) ) {
indentElement( e, i, n, indent )
}
}
N = e.nodeCount()
if( N > 0 ) {
indent = '\n' + indentStep * ( level - 1 )
Node n = e.getNode( N - 1 )
if( XMLUtils.isElement( n ) ) {
// println "Indent closing element ${e.name}"
def textNode = new Text( indent )
e.addNode( textNode )
} else if( XMLUtils.isText( n ) ) {
String text = n.text
if( !text.endsWith( indent ) ) {
if( n.isWhitespace() ) {
n.text = indent
} else {
// Do nothing; this would modify text nodes like '<profile>id</profile>'
}
}
}
}
}
void indentElement( Element parent, int index, Element e, String indent ) {
boolean addNode = true
if( index > 0 ) {
Node previous = parent.getNode( index - 1 )
if( XMLUtils.isText( previous ) ) {
String text = previous.text
// println "indent ${e.name} text=${escape(text)} indent=${escape(indent)} ws=${previous.isWhitespace()}"
if( text.endsWith( indent ) ) {
// println ' same'
addNode = false
} else if( previous.isWhitespace() ) {
// println ' reuse'
previous.text = indent
addNode = false
}
}
}
if( addNode ) {
def textNode = new Text( indent )
parent.addNode( index, textNode )
}
format( e )
}
String escape( String text ) {
StringBuilder buffer = new StringBuilder()
for( int i=0; i<text.size(); i++ ) {
char c = text[i]
switch( c ) {
case '\n': buffer.append( '\\n' ); break
case '\r': buffer.append( '\\r' ); break
case '\t': buffer.append( '\\t' ); break
default: buffer.append( c )
}
}
return buffer.toString()
}
}