Я поддерживаю плагин IntelliJ, который улучшает опыт написания спецификаций Спока . Задача этого проекта — поддержка нескольких несовместимых версий IntelliJ API в одной кодовой базе. Решение простое в ретроспективе (это пример шаблона адаптера в дикой природе), но первоначально потребовалось немного размышлений и поиска примеров. Сегодня я снова был в коде, чтобы исправить поддержку новой версии , и я решил задокументировать, как я изначально решил проблему.
Основная проблема заключается в том, что мой скомпилированный код может быть загружен в среде выполнения JVM с любой из нескольких различных версий API. Моим решением было разделить проект на четыре части:
- Основной проект, который не зависит от каких-либо различных вызовов API и поэтому совместим со всеми версиями API. Основной проект также имеет код, который загружает соответствующую реализацию адаптера на основе среды выполнения, в которой он находится. В этом случае я могу использовать IntelliJ PicoContainer для поиска службы, но API отражения или внедрение зависимостей также имеют что нужно
- Набор абстрактных адаптеров, которые предоставляют API для основного проекта. Этот проект также не зависит от кода, который варьируется в зависимости от версии API.
- Наборы классов, которые реализуют абстрактные адаптеры для каждой поддерживаемой версии API. Каждый набор адаптеров переносит изменяющиеся вызовы API и компилируется с определенной версией API.
Простейший случай, с которым нужно иметь дело, — это рефакторинг, в котором что-то в API перемещается. Это также то, что фактически сломало эту последнюю версию. Моему основному коду нужен Groovy-экземпляр com.intellij.lang.Language. Этот экземпляр перенесен в IntelliJ 14.
Этот код был постоянным до 14, поэтому в этом случае я добавляю новый адаптер. В модуле адаптера у меня есть абстрактный класс LanguageLookup.java :
01
02
03
04
05
06
07
08
09
10
11
|
package com.cholick.idea.spock; import com.intellij.lang.Language; import com.intellij.openapi.components.ServiceManager; public abstract class LanguageLookup { public static LanguageLookup getInstance() { return ServiceManager.getService(LanguageLookup. class ); } public abstract Language groovy(); } |
Самая низкая версия IntelliJ API, которую я поддерживаю, — 11. Поиск экземпляра языка Groovy постоянен для 11-13, поэтому первый конкретный адаптер живет в модуле, скомпилированном с IntelliJ 11 API. LanguageLookup11.java :
01
02
03
04
05
06
07
08
09
10
|
package com.cholick.idea.spock; import com.intellij.lang.Language; import org.jetbrains.plugins.groovy.GroovyFileType; public class LanguageLookup11 extends LanguageLookup { public Language groovy() { return GroovyFileType.GROOVY_LANGUAGE; } } |
В новейшем API появилось критическое изменение, поэтому второй конкретный адаптер живет в модуле, скомпилированном с версией 14 их API. LanguageLookup14.java :
01
02
03
04
05
06
07
08
09
10
|
package com.cholick.idea.spock; import com.intellij.lang.Language; import org.jetbrains.plugins.groovy.GroovyLanguage; public class LanguageLookup14 extends LanguageLookup { public Language groovy() { return GroovyLanguage.INSTANCE; } } |
Наконец, у основного проекта есть класс SpockPluginLoader.java, который регистрирует правильный класс адаптера на основе загруженного API времени выполнения (я опустил несколько методов, не относящихся конкретно к примеру):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
package com.cholick.idea.spock.adapter; import com.cholick.idea.spock.LanguageLookup; import com.cholick.idea.spock.LanguageLookup11; import com.cholick.idea.spock.LanguageLookup14; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.components.impl.ComponentManagerImpl; import org.jetbrains.annotations.NotNull; import org.picocontainer.MutablePicoContainer; public class SpockPluginLoader implements ApplicationComponent { private ComponentManagerImpl componentManager; SpockPluginLoader( @NotNull ComponentManagerImpl componentManager) { this .componentManager = componentManager; } @Override public void initComponent() { MutablePicoContainer picoContainer = componentManager.getPicoContainer(); registerLanguageLookup(picoContainer); } private void registerLanguageLookup(MutablePicoContainer picoContainer) { if (isAtLeast14()) { picoContainer.registerComponentInstance(LanguageLookup. class .getName(), new LanguageLookup14()); } else { picoContainer.registerComponentInstance(LanguageLookup. class .getName(), new LanguageLookup11()); } } private IntelliJVersion getVersion() { int version = ApplicationInfo.getInstance().getBuild().getBaselineVersion(); if (version >= 138 ) { return IntelliJVersion.V14; } else if (version >= 130 ) { return IntelliJVersion.V13; } else if (version >= 120 ) { return IntelliJVersion.V12; } return IntelliJVersion.V11; } private boolean isAtLeast14() { return getVersion().compareTo(IntelliJVersion.V14) >= 0 ; } enum IntelliJVersion { V11, V12, V13, V14 } } |
Наконец, в коде, где мне нужен Groovy com.intellij.lang.Language, я использую сервис LanguageLookup и вызываю его метод groovy:
1
2
3
4
|
... Language groovy = LanguageLookup.getInstance().groovy(); if (PsiUtilBase.getLanguageAtOffset(file, offset).isKindOf(groovy)) { ... |
Это решение позволяет одному и тому же скомпилированному плагину JAR поддерживать различные API IntelliJ в версиях 11-14. Я предполагаю, что разработчики Android обычно внедряют подобные решения, но мне никогда не приходилось писать это как разработчик веб-приложений.
Ссылка: | Загрузка класса времени выполнения для поддержки меняющегося API от нашего партнера по JCG Мэтта Чолика в блоге Cholick.com . |