1   package org.daisy.pipeline.nlp.lexing;
2   
3   import java.util.Collection;
4   import java.util.List;
5   import java.util.Locale;
6   import java.util.Map;
7   import java.util.concurrent.ConcurrentHashMap;
8   import java.util.concurrent.CopyOnWriteArrayList;
9   
10  import org.daisy.pipeline.nlp.lexing.LexService.LexerInitException;
11  import org.daisy.pipeline.nlp.lexing.LexService.LexerToken;
12  
13  import org.osgi.service.component.annotations.Component;
14  import org.osgi.service.component.annotations.Reference;
15  import org.osgi.service.component.annotations.ReferenceCardinality;
16  import org.osgi.service.component.annotations.ReferencePolicy;
17  
18  @Component(
19  	name = "lexer-registry",
20  	service = { LexServiceRegistry.class }
21  )
22  public class LexServiceRegistry {
23  	private Map<LexService, List<LexerToken>> mLexerToTokens = new ConcurrentHashMap<LexService, List<LexerToken>>();
24  
25  	/**
26  	 * Component callback
27  	 */
28  	@Reference(
29  		name = "LexService",
30  		unbind = "-",
31  		service = LexService.class,
32  		cardinality = ReferenceCardinality.MULTIPLE,
33  		policy = ReferencePolicy.STATIC
34  	)
35  	public void addLexService(LexService lexer) {
36  		mLexerToTokens.put(lexer, new CopyOnWriteArrayList<LexService.LexerToken>());
37  	}
38  
39  	/**
40  	 * Component callback
41  	 */
42  	public void removeLexService(LexService lexer) {
43  		List<LexerToken> tokens = mLexerToTokens.get(lexer);
44  		synchronized (this) {
45  			if (tokens.size() > 0) {
46  				lexer.globalRelease();
47  			}
48  		}
49  		mLexerToTokens.remove(lexer);
50  	}
51  
52  	/**
53  	 * Choose a LexService for a language and get its local token or create a
54  	 * new one if there is not yet an existing one.
55  	 * 
56  	 * @param lang
57  	 * @param existingTokens are the current tokens of the pipeline job
58  	 *            previously returned by getTokenForLang().
59  	 * @return a token for the LexService that best works with @param lang. A
60  	 *         token is an instance of lexer which is unique for each pipeline
61  	 *         job so that it can work within multi-thread contexts. It first
62  	 *         attempts to find a token of LexService that is already used by
63  	 *         the pipeline job for other languages. If none is found, it
64  	 *         creates a new token. The token is initialized with the resources
65  	 *         of the other pipeline jobs when this is possible.
66  	 * @throws LexerInitException
67  	 */
68  	public LexerToken getTokenForLang(Locale lang, Collection<LexerToken> existingTokens)
69  	        throws LexerInitException {
70  		// Look in priority in the existing tokens
71  		int bestScore = -1;
72  		LexerToken bestToken = null;
73  		for (LexerToken token : existingTokens) {
74  			int score = token.getLexService().getLexQuality(lang);
75  			if (score > bestScore) {
76  				bestScore = score;
77  				bestToken = token;
78  			}
79  		}
80  
81  		// Look in the others lexers
82  		Map.Entry<LexService, List<LexerToken>> best = null;
83  		for (Map.Entry<LexService, List<LexerToken>> entry : mLexerToTokens.entrySet()) {
84  			int score = entry.getKey().getLexQuality(lang);
85  			if (score > bestScore) {
86  				bestScore = score;
87  				best = entry;
88  				bestToken = null;
89  			}
90  		}
91  
92  		if (bestToken != null) {
93  			//The best token for the lang has already been created.
94  			//We then bind the language to it.
95  			for (LexerToken otherToken : mLexerToTokens.get(bestToken.getLexService())) {
96  				bestToken.shareResourcesWith(otherToken, lang);
97  			}
98  			bestToken.addLang(lang);
99  			return bestToken;
100 		}
101 
102 		//A new token is the best choice for the given language.
103 		LexerToken result = best.getKey().newToken();
104 		result.addLang(lang);
105 		best.getValue().add(result);
106 		synchronized (this) {
107 			if (best.getValue().size() == 1) {
108 				best.getKey().globalInit(); //first initialization of the LexService
109 			}
110 		}
111 
112 		return result;
113 	}
114 
115 	/**
116 	 * Choose a LexService and get its token to be used for cases when languages
117 	 * are unknown or not supported.
118 	 */
119 	public LexerToken getFallbackToken(Collection<LexerToken> existingTokens)
120 	        throws LexerInitException {
121 		// Look in priority in the existing tokens
122 		int bestScore = 0;
123 		LexerToken bestToken = null;
124 		for (LexerToken token : existingTokens) {
125 			int score = token.getLexService().getOverallQuality();
126 			if (score > bestScore) {
127 				bestScore = score;
128 				bestToken = token;
129 			}
130 		}
131 
132 		// Look at the other lexers
133 		Map.Entry<LexService, List<LexerToken>> best = null;
134 		for (Map.Entry<LexService, List<LexerToken>> entry : mLexerToTokens.entrySet()) {
135 			int score = entry.getKey().getOverallQuality();
136 			if (score > bestScore) {
137 				bestScore = score;
138 				best = entry;
139 				bestToken = null;
140 			}
141 		}
142 
143 		if (bestToken != null) {
144 			return bestToken;
145 		}
146 
147 		if (best == null) {
148 			throw new LexerInitException("No generic lexing service available");
149 		}
150 
151 		//The best fallback choice is to use a new token
152 		LexerToken result = best.getKey().newToken();
153 		synchronized (this) {
154 			if (best.getValue().size() == 1) {
155 				best.getKey().globalInit(); //first initialization of the LexService
156 			}
157 		}
158 
159 		return result;
160 	}
161 
162 	/**
163 	 * Unregister a tokens returned by getTokenForLang(). Releasing multiple
164 	 * times the same token has no side effect.
165 	 * 
166 	 * @param jobtokens
167 	 */
168 	public void releaseToken(LexerToken jobtoken) {
169 		for (List<LexerToken> tokens : mLexerToTokens.values()) {
170 			if (tokens.remove(jobtoken)) {
171 				synchronized (this) {
172 					if (tokens.size() == 0) {
173 						jobtoken.getLexService().globalRelease();
174 					}
175 				}
176 				return;
177 			}
178 		}
179 
180 	}
181 
182 }