package com.almworks.jira.structure.util;
import com.almworks.integers.*;
import com.almworks.jira.structure.api.*;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.issue.*;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.util.I18nHelper;
import com.opensymphony.workflow.loader.ActionDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.util.*;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterators.forArray;
import static java.util.Collections.nCopies;
import static java.util.Collections.singleton;
/**
* <p>Contains miscellaneous utility methods.</p>
*/
public class StructureUtil {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(StructureUtil.class);
public static final long GLOBAL_STRUCTURE_ID = 1;
public static final int MAX_PROPERTY_LENGTH = 250;
public static final La<String, Long> STRING_LONG = new La<String, Long>() {
public Long la(String argument) {
try {
return argument == null ? null : Long.parseLong(argument.trim());
} catch (NumberFormatException e) {
return null;
}
}
};
public static final La<IntIterator, Integer> INT_ITERATOR_TO_INT = new La<IntIterator, Integer>() {
@Override
public Integer la(IntIterator intIterator) {
return intIterator.value();
}
};
public static final La<LongIterator, Long> LONG_ITERATOR_TO_LONG = new La<LongIterator, Long>() {
@Override
public Long la(LongIterator longIterator) {
return longIterator.value();
}
};
public static PermissionLevel applyPermissions(List<PermissionRule> permissions, User user,
List<Object> stack, La<Long, List<PermissionRule>> resolver, PermissionLevel pass)
{
PermissionLevel r = pass;
if (permissions != null) {
for (PermissionRule permission : permissions) {
r = permission.apply(user, r, stack, resolver);
}
}
return r;
}
public static JAXBContext createJAXBContext(Class contextClass) {
Thread thread = Thread.currentThread();
ClassLoader contextLoader = thread.getContextClassLoader();
ClassLoader[] loaders = {ClassLoader.getSystemClassLoader(), contextLoader, JAXBContext.class.getClassLoader(), contextClass.getClassLoader()};
JAXBContext context = null;
try {
context = JAXBContext.newInstance(contextClass);
} catch (JAXBException e) {
for (int i = 1; i < loaders.length; i++) {
try {
thread.setContextClassLoader(loaders[i]);
context = JAXBContext.newInstance(contextClass);
if (context != null) break;
} catch (JAXBException e2) {
// ignore
}
}
if (context == null) {
logger.error("cannot initialize JAXB context for " + contextClass + " (tried loaders: " + Arrays.asList(loaders) + ")", e);
}
} finally {
thread.setContextClassLoader(contextLoader);
}
return context;
}
public static Long getSingleParameterLong(Map map, String name) {
String value = getSingleParameter(map, name);
if (value != null && value.length() > 0) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
logger.warn("bad parameter " + name + " [" + value + "]");
}
}
return null;
}
public static Integer getSingleParameterInteger(Map map, String name) {
String value = getSingleParameter(map, name);
if (value != null && value.length() > 0) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
logger.warn("bad parameter " + name + " [" + value + "]");
}
}
return null;
}
public static boolean getSingleParameterBoolean(Map map, String name) {
String value = getSingleParameter(map, name);
if (value == null) return false;
return "true".equalsIgnoreCase(value.trim());
}
public static String getSingleParameter(Map map, String name) {
Object r = map.get(name);
if (r instanceof Collection) {
Collection c = (Collection) r;
r = c.isEmpty() ? null : c.iterator().next();
} else if (r instanceof String[]) {
r = ((String[]) r).length == 0 ? null : ((String[]) r)[0];
}
return r == null ? null : String.valueOf(r);
}
@NotNull
public static List<String> getMultiParameter(Map map, String name) {
Object r = map.get(name);
if (r instanceof Collection) {
Collection c = (Collection) r;
ArrayList<String> result = new ArrayList<String>(c.size());
for (Object o : c) {
if (o != null) result.add(String.valueOf(o));
}
return result;
} else if (r instanceof String[]) {
String[] array = (String[]) r;
return array.length == 0 ? Collections.<String>emptyList() : Arrays.asList(array);
}
return r == null ? Collections.<String>emptyList() : Collections.singletonList(String.valueOf(r));
}
public static List<Long> getMultiParameterLong(Map map, String name) {
return STRING_LONG.arrayList(getMultiParameter(map, name));
}
/**
* Sets a possibly long value (template) as an application property. Because properties may be limited to 255
* characters, this method breaks up long property into several properties each no longer than allowed.
*
* @param properties properties interface
* @param name the name of the property
* @param value the value
*/
public static void setLongProperty(ApplicationProperties properties, String name, String value) {
if (value == null) value = "";
int len = value.length();
int chunk = Math.min(len, MAX_PROPERTY_LENGTH);
properties.setString(name, value.substring(0, chunk));
value = value.substring(chunk);
len -= chunk;
int step = 1;
while (len > 0) {
chunk = Math.min(len, MAX_PROPERTY_LENGTH);
properties.setString(name + "." + (step++), value.substring(0, chunk));
value = value.substring(chunk);
len -= chunk;
}
while (true) {
String n = name + "." + (step++);
String s = properties.getDefaultBackedString(n);
if (s == null || s.length() <= 0) {
break;
}
properties.setString(n, null);
}
}
/**
* Reads a value previously stored in properties using setLongValue
*
* @param properties application propeties
* @param name the name of the property to read
* @return property value or an empty string
*/
public static String getLongProperty(ApplicationProperties properties, String name) {
StringBuilder r = new StringBuilder();
String s = properties.getDefaultBackedString(name);
int step = 1;
while (s != null && s.length() > 0) {
r.append(s);
s = properties.getDefaultBackedString(name + "." + (step++));
}
return r.toString();
}
/**
* Returns the last element of the given list if it's not empty, 0 otherwise.
*
* @param list the list of longs
* @return last value of <code>list</code> or 0 if it's empty.
*/
public static long lastOrZero(LongList list) {
return (list == null || list.isEmpty()) ? 0 : list.get(list.size() - 1);
}
/**
* Tests whether the given lists are equal (contain the same numbers in the same order).
* An empty list is considered equal to a <code>null</code> list.
*
* @param list1 the first list
* @param list2 the second list
* @return <code>true</code> if the lists are equal, <code>false</code> otherwise.
*/
public static boolean listsEqual(LongList list1, LongList list2) {
boolean empty1 = list1 == null || list1.isEmpty();
boolean empty2 = list2 == null || list2.isEmpty();
if (empty1 && empty2) return true;
if (empty1 || empty2) return false;
return list1.equals(list2);
}
@Nullable
public static List<PermissionRule> copyPermissionsOrNull(@Nullable Collection<? extends PermissionRule> permissions) {
return permissions == null ? null : copyPermissions(permissions);
}
@NotNull
public static List<PermissionRule> copyPermissions(@Nullable Collection<? extends PermissionRule> permissions) {
ArrayList<PermissionRule> r = new ArrayList<PermissionRule>(permissions == null ? 0 : permissions.size());
if (permissions != null) {
for (PermissionRule permission : permissions) {
r.add(permission.clone());
}
}
return r;
}
/**
* Returns a string representation of the issue that is used to write log messages. Writes issue ID and, if possible,
* issue key.
* @param issue the ID of the issue
* @return string that can be used in output
*/
@NotNull
public static String getDebugIssueString(@Nullable Long issue) {
return issue == null ? "null" : getDebugIssueString(issue, getDebugIssueKey(issue));
}
/** Returns issue key if possible. Use for debug output. */
@Nullable
public static String getDebugIssueKey(long issueId) {
try {
IssueManager issueManager = ComponentAccessor.getIssueManager();
MutableIssue io = issueManager == null ? null : issueManager.getIssueObject(issueId);
return io == null ? null : io.getKey();
} catch (Exception ignored) {}
return null;
}
/**
* Returns a string representation of the issue that is used to write log messages. Writes issue ID and, if possible,
* issue key.
* @param issue the issue
* @return string that can be used in output
*/
@NotNull
public static String getDebugIssueString(@Nullable Issue issue) {
return issue == null ? "null" : getDebugIssueString(issue.getId(), issue.getKey());
}
/**
* @return issue key if the issue object is not {@code null}. Otherwise, if issue ID is not {@code null}, returns issue ID in the form "issue #123". Otherwise,
* returns string "(no issue ID)".
* Better suited for high-level log messages than {@link #getDebugIssueString(java.lang.Long)} in that it is easily recognizable as issue representation to JIRA admin.
* */
public static String issueKeyOrId(@Nullable Issue issue, @Nullable Long issueId) {
return issue != null ? issue.getKey() : issueId != null ? "issue #" + issueId : "(no issue ID)";
}
@NotNull
private static String getDebugIssueString(@Nullable Long issueId, @Nullable String issueKey) {
StringBuilder r = new StringBuilder("(");
if (issueKey != null) {
r.append(issueKey).append(' ');
}
r.append(issueId);
r.append(')');
return r.toString();
}
@NotNull
public static StringBuilder appendDebugStructureString(StringBuilder sb, long structureId, StructureManager manager) {
try {
Structure structure = manager.getStructure(structureId, null, null, true);
if (structure != null) {
String name = structure.getName();
return sb.append('\'').append(name).append("' (#").append(structureId).append(')');
}
} catch (StructureException ignored) {
}
return sb.append("#").append(structureId);
}
@NotNull
public static String getDebugStructureString(long structureId, StructureManager manager) {
return appendDebugStructureString(new StringBuilder(), structureId, manager).toString();
}
@NotNull
public static String username(@Nullable User user) {//j6 ok
String username = user != null ? user.getName() : "anonymous"; //j6 ok
return username == null ? "user[name = null]" : username;
}
public static String debugConstant(IssueConstant constant) {
return constant == null ? "<null>" : constant.getName() + "(" + constant.getId() + ")";
}
public static String debugAction(ActionDescriptor action) {
return action == null ? "<null>" : action.getName() + "(" + action.getId() + ")";
}
public static String getTextInCurrentUserLocale(String key, Object... parameters) {
JiraAuthenticationContext context = ComponentAccessor.getJiraAuthenticationContext();
if (context == null) logger.warn("No JIRA auth context: maybe it is a plugin system 2 plugin now? Text will be formatted in the JIRA default locale.");
User user = context == null ? null : context.getLoggedInUser();
return getText(null, user, key, parameters);
}
/**
* Formats the text taken from the i18n bundle with the specified parameters in the locale that is selected as follows:
* <ol>
* <li>If <tt>locale</tt> is not <tt>null</tt>, it is used.</li>
* <li>Otherwise, if <tt>user</tt> is not <tt>null</tt>, the user locale (set up in the user preferences) is used.</li>
* <li>If both <tt>locale</tt> and <tt>user</tt> are <tt>null</tt>, JIRA default locale is used. In a highly unlikely
* event when JIRA application parameters are inaccessible, {@link java.util.Locale#ROOT root} (neutral) locale is used.</li>
* </ol>
* @param messageParameters can be the same as {@link com.atlassian.jira.util.I18nHelper#getText(String, Object)}
* */
public static String getText(@Nullable Locale locale, @Nullable User user, String textKey, Object... messageParameters) {
I18nHelper.BeanFactory factory = ComponentAccessor.getI18nHelperFactory();
if (factory == null) return textKey + ' ' + Arrays.toString(messageParameters);
I18nHelper i18nHelper = locale != null || user == null
? factory.getInstance(locale != null ? locale : getJiraDefaultLocale())
: factory.getInstance(user);
return i18nHelper.getText(textKey, messageParameters);
}
public static Locale getJiraDefaultLocale() {
ApplicationProperties appProps = ComponentAccessor.getApplicationProperties();
if (appProps == null) {
logger.warn("No application properties: probably a System 2 plugin now? Using root (neutral) locale.");
return Locale.ROOT;
}
return appProps.getDefaultLocale();
}
}