/******************************************************************************* * Copyright (c) 2015 IBH SYSTEMS GmbH. * 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: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.utils.validation; import java.util.LinkedList; import java.util.Locale; import java.util.Locale.Category; import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.ResourceBundle; import javax.validation.MessageInterpolator; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.wiring.BundleWiring; import org.osgi.util.tracker.BundleTracker; import org.osgi.util.tracker.BundleTrackerCustomizer; public class OsgiMessageInterpolator implements MessageInterpolator { private static class Resolver { private final ClassLoader classLoader; public Resolver ( final Bundle bundle ) { this.classLoader = bundle.adapt ( BundleWiring.class ).getClassLoader (); } public String resolve ( final String name, final Context context, final Locale locale ) { try { final ResourceBundle resourceBundle = ResourceBundle.getBundle ( "META-INF/ValidationMessages", locale != null ? locale : Locale.getDefault (), this.classLoader ); return resourceBundle.getString ( name ); } catch ( final MissingResourceException e ) { return null; } } public void dispose () { ResourceBundle.clearCache ( this.classLoader ); } } private MessageInterpolator fallback; private BundleTracker<Resolver> tracker; public OsgiMessageInterpolator ( final BundleContext context ) { this.tracker = new BundleTracker<> ( context, Bundle.ACTIVE | Bundle.RESOLVED, new BundleTrackerCustomizer<Resolver> () { @Override public Resolver addingBundle ( final Bundle bundle, final BundleEvent event ) { if ( bundle.getResource ( "META-INF/ValidationMessages.properties" ) != null ) { return new Resolver ( bundle ); } return null; } @Override public void modifiedBundle ( final Bundle bundle, final BundleEvent event, final Resolver resolver ) { } @Override public void removedBundle ( final Bundle bundle, final BundleEvent event, final Resolver resolver ) { resolver.dispose (); } } ); this.tracker.open (); } public void dispose () { this.tracker.close (); } public void setFallback ( final MessageInterpolator fallback ) { this.fallback = fallback; } @Override public String interpolate ( final String message, final Context context ) { return interpolate ( message, context, Locale.getDefault ( Category.DISPLAY ) ); } @Override public String interpolate ( final String message, final Context context, final Locale locale ) { final StringBuilder sb = new StringBuilder (); boolean escaped = false; final LinkedList<StringBuilder> keyStack = new LinkedList<> (); for ( int i = 0; i < message.length (); i++ ) { final char c = message.charAt ( i ); if ( escaped ) { escaped = false; sb.append ( c ); } else { switch ( c ) { case '\\': escaped = true; break; case '{': keyStack.push ( new StringBuilder () ); break; case '}': try { final StringBuilder keySb = keyStack.pop (); sb.append ( resolve ( keySb.toString (), context, locale ) ); } catch ( final NoSuchElementException e ) { sb.append ( '}' ); } break; default: if ( keyStack.isEmpty () ) { sb.append ( c ); } else { keyStack.peek ().append ( c ); } break; } } } return sb.toString (); } protected String resolve ( final String key, final Context context, final Locale locale ) { for ( final Resolver resolver : this.tracker.getTracked ().values () ) { final String result = resolver.resolve ( key, context, locale ); if ( result != null ) { return result; } } final MessageInterpolator fallback = this.fallback; if ( fallback == null ) { return null; } return fallback.interpolate ( String.format ( "{%s}", key ), context, locale ); } }